Skip to content

Commit

Permalink
Merge e0bc526 into 37e1b5c
Browse files Browse the repository at this point in the history
  • Loading branch information
jonathanhogg committed Jul 24, 2020
2 parents 37e1b5c + e0bc526 commit 2bc7bf7
Show file tree
Hide file tree
Showing 2 changed files with 143 additions and 65 deletions.
53 changes: 33 additions & 20 deletions docs/library/esp32.rst
Expand Up @@ -152,9 +152,8 @@ used to transmit or receive many other types of digital signals::
r = esp32.RMT(0, pin=Pin(18), clock_div=8)
r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8)

# To use carrier frequency
r = esp32.RMT(0, pin=Pin(18), clock_div=8, carrier_freq=38000)
r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8, carrier_freq=38000, carrier_duty_percent=50)
# To apply a carrier frequency to the high output
r = esp32.RMT(0, pin=Pin(18), clock_div=8, tx_carrier=(38000, 50, 1)

# The channel resolution is 100ns (1/(source_freq/clock_div)).
r.write_pulses((1, 20, 2, 40), start=0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns
Expand All @@ -169,9 +168,6 @@ define the pulses.
multiplying the resolution by a 15-bit (0-32,768) number. There are eight
channels (0-7) and each can have a different clock divider.

To enable the carrier frequency feature of the esp32 hardware, specify the
``carrier_freq`` as something like 38000, a typical IR carrier frequency.

So, in the example above, the 80MHz clock is divided by 8. Thus the
resolution is (1/(80Mhz/8)) 100ns. Since the ``start`` level is 0 and toggles
with each number, the bitstream is ``0101`` with durations of [100ns, 2000ns,
Expand All @@ -186,16 +182,19 @@ For more details see Espressif's `ESP-IDF RMT documentation.
*beta feature* and the interface may change in the future.


.. class:: RMT(channel, \*, pin=None, clock_div=8, carrier_freq=0, carrier_duty_percent=50)
.. class:: RMT(channel, \*, pin=None, clock_div=8, idle_level=0, tx_carrier=None)

This class provides access to one of the eight RMT channels. *channel* is
required and identifies which RMT channel (0-7) will be configured. *pin*,
also required, configures which Pin is bound to the RMT channel. *clock_div*
is an 8-bit clock divider that divides the source clock (80MHz) to the RMT
channel allowing the resolution to be specified. *carrier_freq* is used to
enable the carrier feature and specify its frequency, default value is ``0``
(not enabled). To enable, specify a positive integer. *carrier_duty_percent*
defaults to 50.
channel allowing the resolution to be specified. *idle_level* specifies
what level the output will be when no transmission is in progress (``0`` or
``1``); set to ``None`` to disable idle output.

To enable the transmission carrier feature, *tx_carrier* should be a tuple
of three positive integers: carrier frequency, duty percent (``0`` to
``100``) and the output level (``0`` or ``1``) to apply the carrier to.

.. method:: RMT.source_freq()

Expand Down Expand Up @@ -223,23 +222,37 @@ For more details see Espressif's `ESP-IDF RMT documentation.
Configure looping on the channel. *enable_loop* is bool, set to ``True`` to
enable looping on the *next* call to `RMT.write_pulses`. If called with
``False`` while a looping stream is currently being transmitted then the
current set of pulses will be completed before transmission stops.
current set of pulses will be completed and then transmission will stop.

.. method:: RMT.write_pulses(duration, data=None, \*, start=None)

Begin sending a stream of pulses. There are three ways to specify these
pulses:

**Method 1:** *duration* is a positive integer and *data* is a list or tuple
of output levels (``0`` or ``1``). *duration* specifies a fixed length for
each pulse as a number to be multiplied by the channel resolution
``(1 / (source_freq / clock_div))``. The optional *start* keyword argument
is not used.

.. method:: RMT.write_pulses(pulses, start)
**Method 2:** *duration* is a list or tuple of integer pulse durations,
again in units of the channel resolution. If the *start* keyword-only
argument is provided then it specifies the initial output level (``0``
or ``1``); if not provided (or ``None``) then the output level will start at
``1``. The output level will toggle after each pulse duration.

Begin sending *pulses*, a list or tuple defining the stream of pulses. The
length of each pulse is defined by a number to be multiplied by the channel
resolution ``(1 / (source_freq / clock_div))``. *start* defines whether the
stream starts at 0 or 1.
**Method 3:** *duration* and *data* are lists or tuples of equal length,
specifying both the length and output level of each pulse. The optional
*start* keyword argument is not used.

If transmission of a stream is currently in progress then this method will
block until transmission of that stream has ended before beginning sending
*pulses*.
block until transmission of that stream has ended before beginning the new
pulses.

If looping is enabled with `RMT.loop`, the stream of pulses will be repeated
indefinitely. Further calls to `RMT.write_pulses` will end the previous
stream - blocking until the last set of pulses has been transmitted -
before starting the next stream.
and then start the new repeating stream.


Ultra-Low-Power co-processor
Expand Down
155 changes: 110 additions & 45 deletions ports/esp32/esp32_rmt.c
Expand Up @@ -44,6 +44,9 @@
// This current MicroPython implementation lacks some major features, notably receive pulses
// and carrier output.

// rmt_item32_t.duration0 and rmt_item32_t.duration1 are 15 bit unsigned values
#define RMT_DURATION_MAX 32767

// Forward declaration
extern const mp_obj_type_t esp32_rmt_type;

Expand All @@ -52,8 +55,6 @@ typedef struct _esp32_rmt_obj_t {
uint8_t channel_id;
gpio_num_t pin;
uint8_t clock_div;
uint16_t carrier_duty_percent;
uint32_t carrier_freq;
mp_uint_t num_items;
rmt_item32_t *items;
bool loop_en;
Expand All @@ -64,51 +65,66 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz
{ MP_QSTR_id, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = -1} },
{ MP_QSTR_pin, MP_ARG_REQUIRED | MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_clock_div, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 8} }, // 100ns resolution
{ MP_QSTR_carrier_duty_percent, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 50} },
{ MP_QSTR_carrier_freq, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
{ MP_QSTR_idle_level, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, // 0 as default
{ MP_QSTR_tx_carrier, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
};
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all_kw_array(n_args, n_kw, all_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
mp_uint_t channel_id = args[0].u_int;
gpio_num_t pin_id = machine_pin_get_id(args[1].u_obj);
mp_uint_t clock_div = args[2].u_int;
mp_uint_t idle_level = args[3].u_int;
mp_obj_t tx_carrier_obj = args[4].u_obj;

bool carrier_en = false;
mp_uint_t carrier_duty_percent = 0;
mp_uint_t carrier_freq = 0;
rmt_config_t config;

if (args[4].u_int > 0) {
carrier_en = true;
carrier_duty_percent = args[3].u_int;
carrier_freq = args[4].u_int;
if (clock_div < 1 || clock_div > 255) {
mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be 1..255"));
}

if (clock_div < 1 || clock_div > 255) {
mp_raise_ValueError(MP_ERROR_TEXT("clock_div must be between 1 and 255"));
if (tx_carrier_obj != mp_const_none) {
mp_obj_t *tx_carrier_details = NULL;
mp_obj_get_array_fixed_n(tx_carrier_obj, 3, &tx_carrier_details);
mp_int_t frequency = mp_obj_get_int(tx_carrier_details[0]);
mp_uint_t duty = mp_obj_get_int(tx_carrier_details[1]);
mp_uint_t level = mp_obj_get_int(tx_carrier_details[2]);

if (frequency <= 0) {
mp_raise_ValueError(MP_ERROR_TEXT("tx_carrier frequency must be >0"));
}
if (duty < 0 || duty > 100) {
mp_raise_ValueError(MP_ERROR_TEXT("tx_carrier duty must be 0..100"));
}
if (level < 0 || level > 1) {
mp_raise_ValueError(MP_ERROR_TEXT("tx_carrier level must be 0 or 1"));
}

config.tx_config.carrier_en = 1;
config.tx_config.carrier_freq_hz = frequency;
config.tx_config.carrier_duty_percent = duty;
config.tx_config.carrier_level = level;
} else {
config.tx_config.carrier_en = 0;
}

esp32_rmt_obj_t *self = m_new_obj_with_finaliser(esp32_rmt_obj_t);
self->base.type = &esp32_rmt_type;
self->channel_id = channel_id;
self->pin = pin_id;
self->clock_div = clock_div;
self->carrier_duty_percent = carrier_duty_percent;
self->carrier_freq = carrier_freq;
self->loop_en = false;

rmt_config_t config;
config.rmt_mode = RMT_MODE_TX;
config.channel = (rmt_channel_t)self->channel_id;
config.gpio_num = self->pin;
config.mem_block_num = 1;
config.tx_config.loop_en = 0;

config.tx_config.carrier_en = carrier_en;
if (idle_level < 0 || idle_level > 1) {
mp_raise_ValueError(MP_ERROR_TEXT("idle_level must be 0 or 1"));
}
config.tx_config.idle_output_en = 1;
config.tx_config.idle_level = 0;
config.tx_config.carrier_duty_percent = self->carrier_duty_percent;
config.tx_config.carrier_freq_hz = self->carrier_freq;
config.tx_config.carrier_level = 1;
config.tx_config.idle_level = idle_level;

config.clk_div = self->clock_div;

Expand All @@ -121,14 +137,8 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz
STATIC void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in);
if (self->pin != -1) {
mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u",
mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u)",
self->channel_id, self->pin, APB_CLK_FREQ, self->clock_div);
if (self->carrier_freq > 0) {
mp_printf(print, ", carrier_freq=%u, carrier_duty_percent=%u)",
self->carrier_freq, self->carrier_duty_percent);
} else {
mp_printf(print, ")");
}
} else {
mp_printf(print, "RMT()");
}
Expand Down Expand Up @@ -197,39 +207,94 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_obj, esp32_rmt_loop);

STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_self, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_pulses, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 1} },
{ MP_QSTR_self, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_duration, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_data, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_start, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_obj = mp_const_none} },
};

mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);

esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(args[0].u_obj);
mp_obj_t pulses = args[1].u_obj;
mp_uint_t start = args[2].u_int;

if (start < 0 || start > 1) {
mp_raise_ValueError(MP_ERROR_TEXT("start must be 0 or 1"));
}
// # Method 1: Constant duration
// duration = 10000
// data = (1,0,1,1,1,0,1,0,1)
//
// # Method 2: Always toggle (current implementation)
// duration = (8000,11000,8000,11000,6000,13000,6000,3000,8000)
//
// # Method 3: Most flexi
// duration = (400,200,100,300,200,400)
// data = (1,0,1,1,0,1)
//
// Note: start is only valid for #2.

mp_obj_t duration_obj = args[1].u_obj;
mp_obj_t data_obj = args[2].u_obj;
mp_obj_t start_obj = args[3].u_obj;
mp_uint_t start = 1;

mp_int_t fixed_duration = 0;
size_t duration_length = 0;
size_t data_length = 0;
mp_obj_t *duration_ptr = NULL;
mp_obj_t *data_ptr = NULL;
size_t num_pulses = 0;

if (data_obj == mp_const_none) {
// Method 2: Only method that doesn't use data
mp_obj_get_array(duration_obj, &duration_length, &duration_ptr);
num_pulses = duration_length;

if (start_obj != mp_const_none) {
start = mp_obj_get_int(start_obj);
if (start < 0 || start > 1) {
mp_raise_ValueError(MP_ERROR_TEXT("start must be 0 or 1"));
}
}
} else {
// Method 1 or 3: data is supplied. Must be an array.
mp_obj_get_array(data_obj, &data_length, &data_ptr);
num_pulses = data_length;

size_t pulses_length = 0;
mp_obj_t *pulses_ptr = NULL;
mp_obj_get_array(pulses, &pulses_length, &pulses_ptr);
if (start_obj != mp_const_none) {
mp_raise_ValueError(MP_ERROR_TEXT("start not compatible with data"));
}

mp_uint_t num_items = (pulses_length / 2) + (pulses_length % 2);
if (mp_obj_is_int(duration_obj)) {
// Method 1: duration is a single int
fixed_duration = mp_obj_get_int(duration_obj);
if (fixed_duration < 1 || fixed_duration > RMT_DURATION_MAX) {
mp_raise_ValueError(MP_ERROR_TEXT("duration must be 1.." MP_STRINGIFY(RMT_DURATION_MAX)));
}
} else {
// Method 3: duration is an array
mp_obj_get_array(duration_obj, &duration_length, &duration_ptr);
if (duration_length != data_length) {
mp_raise_ValueError(MP_ERROR_TEXT("duration and data must be same length"));
}
}
}

mp_uint_t num_items = (num_pulses / 2) + (num_pulses % 2);
if (num_items > self->num_items) {
self->items = (rmt_item32_t *)m_realloc(self->items, num_items * sizeof(rmt_item32_t *));
self->num_items = num_items;
}

for (mp_uint_t item_index = 0; item_index < num_items; item_index++) {
mp_uint_t pulse_index = item_index * 2;
self->items[item_index].duration0 = mp_obj_get_int(pulses_ptr[pulse_index++]);
self->items[item_index].level0 = start++; // Note that start _could_ wrap.
if (pulse_index < pulses_length) {
self->items[item_index].duration1 = mp_obj_get_int(pulses_ptr[pulse_index]);
self->items[item_index].level1 = start++;
self->items[item_index].duration0 = fixed_duration /* method 1 */ ? fixed_duration : mp_obj_get_int(duration_ptr[pulse_index]);
self->items[item_index].level0 = data_obj == mp_const_none /* method 2 */ ? start++ : mp_obj_get_int(data_ptr[pulse_index]);
pulse_index++;
if (pulse_index < num_pulses) {
self->items[item_index].duration1 = fixed_duration /* method 1 */ ? fixed_duration : mp_obj_get_int(duration_ptr[pulse_index]);
self->items[item_index].level1 = data_obj == mp_const_none /* method 2 */ ? start++ : mp_obj_get_int(data_ptr[pulse_index]);
} else {
self->items[item_index].duration1 = 0;
self->items[item_index].level1 = 0;
}
}

Expand Down

0 comments on commit 2bc7bf7

Please sign in to comment.