From af6bb8aa6b3163776ba504c32a1c8e537673577c Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Mon, 23 Sep 2019 13:50:58 +1000 Subject: [PATCH 01/20] First commit of RMT tx implementation --- ports/esp32/Makefile | 1 + ports/esp32/esp32_rmt.c | 169 ++++++++++++++++++++++++++++++++++++++++ ports/esp32/modesp32.c | 2 + ports/esp32/modesp32.h | 2 +- 4 files changed, 173 insertions(+), 1 deletion(-) create mode 100644 ports/esp32/esp32_rmt.c diff --git a/ports/esp32/Makefile b/ports/esp32/Makefile index e3b14495d5fb5..8b02a64b45253 100644 --- a/ports/esp32/Makefile +++ b/ports/esp32/Makefile @@ -324,6 +324,7 @@ SRC_C = \ modesp.c \ esp32_partition.c \ esp32_ulp.c \ + esp32_rmt.c \ modesp32.c \ espneopixel.c \ machine_hw_spi.c \ diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c new file mode 100644 index 0000000000000..e360d25d2391d --- /dev/null +++ b/ports/esp32/esp32_rmt.c @@ -0,0 +1,169 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 "Matt Trentini" + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" + +#include "driver/rmt.h" + +#include "py/runtime.h" +#include "modmachine.h" +#include "mphalport.h" + +// Forward declaration +extern const mp_obj_type_t esp32_rmt_type; + +typedef struct _esp32_rmt_obj_t { + mp_obj_base_t base; + uint8_t channel_id; + gpio_num_t pin; + uint8_t clock_divider; +} esp32_rmt_obj_t; + +STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // fixme: check arguments + // 1 <= channel_id <= 8 + // 1 <= clock_divider <= 255 + mp_arg_check_num(n_args, n_kw, 3, MP_OBJ_FUN_ARGS_MAX, true); + mp_uint_t channel_id = mp_obj_get_int(args[0]); + gpio_num_t pin_id = machine_pin_get_id(args[1]); + mp_uint_t clock_divider = mp_obj_get_int(args[2]); + // fixme: should have an idle_level... + + esp32_rmt_obj_t *self = m_new_obj(esp32_rmt_obj_t); + self->base.type = &esp32_rmt_type; + self->channel_id = channel_id; + self->pin = pin_id; + self->clock_divider = clock_divider; + + rmt_config_t config; + config.rmt_mode = RMT_MODE_TX; + config.channel = self->channel_id; // Technically should use enum eg RMT_CHANNEL_0 + config.gpio_num = self->pin; + config.mem_block_num = 1; + config.tx_config.loop_en = 0; + + // Shouldn't be necessary to set these (until we use them...) + // config.tx_config.carrier_en = 0; + // config.tx_config.idle_output_en = 0; + // config.tx_config.idle_level = 0; + // config.tx_config.carrier_duty_percent = 0; + // config.tx_config.carrier_freq_hz = 0; + // config.tx_config.carrier_level = 1; + + config.clk_div = self->clock_divider; + + // fixme: Error handling should be nicer; raise an exception + ESP_ERROR_CHECK(rmt_config(&config)); + ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0)); + + return MP_OBJ_FROM_PTR(self); +} + + +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); + mp_printf(print, "RMT(channel=%u, pin=%u, clock_divider=%u)", self->channel_id, self->pin, self->clock_divider); +} + + +// Return the resolution (shortest possible pulse), in seconds +STATIC mp_obj_t esp32_rmt_resolution(mp_obj_t self_in) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_float(1.0/(APB_CLK_FREQ / self->clock_divider)); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_resolution_obj, esp32_rmt_resolution); + + +// Return the longest pulse duration +STATIC mp_obj_t esp32_rmt_max_duration(mp_obj_t self_in) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_float((1.0/(APB_CLK_FREQ / self->clock_divider)) * 32768); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_max_duration_obj, esp32_rmt_max_duration); + + +STATIC mp_obj_t esp32_rmt_send_pulses(mp_obj_t self_in, mp_obj_t pulses, mp_obj_t start_level) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + mp_uint_t level = mp_obj_get_int(start_level); + // fixme: Check inputs + // start_level => [0,1] + + mp_uint_t pulses_length = 0; + mp_obj_t* pulses_ptr = NULL; + + // fixme: Handle if not a tuple + if(MP_OBJ_IS_TYPE(pulses, &mp_type_tuple) == true) { + mp_obj_tuple_get(pulses, &pulses_length, &pulses_ptr); + } + + mp_uint_t num_items = (pulses_length / 2) + (pulses_length % 2); + rmt_item32_t* items = (rmt_item32_t*)m_malloc(num_items * sizeof(rmt_item32_t)); + + printf("num_items=%d pulses_length=%d\n\n", num_items, pulses_length); + + for (mp_uint_t item_index = 0; item_index < num_items; item_index++) + { + mp_uint_t pulse_index = item_index * 2; + items[item_index].duration0 = mp_obj_get_int(pulses_ptr[pulse_index++]); + items[item_index].level0 = level++; // Note that level _could_ wrap. + printf("duration=%d level=%d\n", items[item_index].duration0, items[item_index].level0); + if (pulse_index < pulses_length) + { + items[item_index].duration1 = mp_obj_get_int(pulses_ptr[pulse_index]); + items[item_index].level1 = level++; + printf("duration=%d level=%d\n", items[item_index].duration1, items[item_index].level1); + } + } + ESP_ERROR_CHECK(rmt_write_items(self->channel_id, items, num_items, true)); + + m_free(items); + + // if (result != ESP_OK) { + // nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "rmt_write_items failed!")); + // } + + return MP_OBJ_NEW_SMALL_INT(num_items); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_rmt_send_pulses_obj, esp32_rmt_send_pulses); + + +STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_send_pulses), MP_ROM_PTR(&esp32_rmt_send_pulses_obj) }, + { MP_ROM_QSTR(MP_QSTR_resolution), MP_ROM_PTR(&esp32_rmt_resolution_obj) }, + { MP_ROM_QSTR(MP_QSTR_max_duration), MP_ROM_PTR(&esp32_rmt_max_duration_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(esp32_rmt_locals_dict, esp32_rmt_locals_dict_table); + +const mp_obj_type_t esp32_rmt_type = { + { &mp_type_type }, + .name = MP_QSTR_RMT, + .print = esp32_rmt_print, + .make_new = esp32_rmt_make_new, + .locals_dict = (mp_obj_dict_t *)&esp32_rmt_locals_dict, +}; + +// ch1 = RMT(1, Pin(18), 80, 0) # channel, pin, clock_divider, idle_level +// ch1.send_items((1000, 500, 1000), start=0) \ No newline at end of file diff --git a/ports/esp32/modesp32.c b/ports/esp32/modesp32.c index ddc030e3fb6ae..e9f856736ca18 100644 --- a/ports/esp32/modesp32.c +++ b/ports/esp32/modesp32.c @@ -157,6 +157,8 @@ 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_ULP), MP_ROM_PTR(&esp32_ulp_type) }, + { MP_ROM_QSTR(MP_QSTR_RMT), MP_ROM_PTR(&esp32_rmt_type) }, + { MP_ROM_QSTR(MP_QSTR_WAKEUP_ALL_LOW), MP_ROM_PTR(&mp_const_false_obj) }, { MP_ROM_QSTR(MP_QSTR_WAKEUP_ANY_HIGH), MP_ROM_PTR(&mp_const_true_obj) }, }; diff --git a/ports/esp32/modesp32.h b/ports/esp32/modesp32.h index 26eec8ae69cea..eb7642cb8688d 100644 --- a/ports/esp32/modesp32.h +++ b/ports/esp32/modesp32.h @@ -28,5 +28,5 @@ extern const mp_obj_type_t esp32_partition_type; extern const mp_obj_type_t esp32_ulp_type; - +extern const mp_obj_type_t esp32_rmt_type; #endif // MICROPY_INCLUDED_ESP32_MODESP32_H From 2d9f0bf892fc3f610e39b3ec96aa5452eb3e6163 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Tue, 24 Sep 2019 13:55:39 +1000 Subject: [PATCH 02/20] Add TODO, complete config init, add deinit method, tidy up resolution calculation. --- ports/esp32/esp32_rmt.c | 69 +++++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 17 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index e360d25d2391d..e375d1fd372ec 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -3,7 +3,7 @@ * * The MIT License (MIT) * - * Copyright (c) 2018 "Matt Trentini" + * Copyright (c) 2019 "Matt Trentini" * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal @@ -42,6 +42,30 @@ typedef struct _esp32_rmt_obj_t { uint8_t clock_divider; } esp32_rmt_obj_t; + +// TODO +// +// o Check all the input parameters +// o Return exceptions when errors occur +// o Manage an array of channels (so user can't create the same one repeatedly) +// - Check if channel is initialised (in particular: after deinit) +// o Investigate: Send without blocking? (Need an 'is_sending' method) +// o Add debug option? Emit printf debug messages? +// - Idea: Generate timing diagram (maybe with https://wavedrom.com/ ?) Extension. +// o Consider: Auto-manage RMT channels? Currently user chooses a channel +// - Alternative: Give the user the next available channel +// o Raise exception if pulses is not a tuple +// - Consider: Using iterables. Will make memory management much trickier though +// - Consider: Allowing lists as well as tuples +// o Do we need to allow the option to use multiple memory blocks? +// o Consider supporting other ways to specify pulses: +// - Fixed interval (specify pattern of 1's/0's) +// - Specify both value and ticks +// - See PyCom RMT docs +// o Add carrier modulation methods +// o Add rx support + + STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { // fixme: check arguments // 1 <= channel_id <= 8 @@ -60,18 +84,17 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz rmt_config_t config; config.rmt_mode = RMT_MODE_TX; - config.channel = self->channel_id; // Technically should use enum eg RMT_CHANNEL_0 + config.channel = (rmt_channel_t) self->channel_id; config.gpio_num = self->pin; config.mem_block_num = 1; config.tx_config.loop_en = 0; - // Shouldn't be necessary to set these (until we use them...) - // config.tx_config.carrier_en = 0; - // config.tx_config.idle_output_en = 0; - // config.tx_config.idle_level = 0; - // config.tx_config.carrier_duty_percent = 0; - // config.tx_config.carrier_freq_hz = 0; - // config.tx_config.carrier_level = 1; + config.tx_config.carrier_en = 0; + config.tx_config.idle_output_en = 0; + config.tx_config.idle_level = 0; + config.tx_config.carrier_duty_percent = 0; + config.tx_config.carrier_freq_hz = 0; + config.tx_config.carrier_level = 1; config.clk_div = self->clock_divider; @@ -89,10 +112,24 @@ STATIC void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ } +STATIC mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { + // fixme: check for valid channel. Return exception if error occurs. + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + rmt_driver_uninstall(self->channel_id); + + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); + +static float resolution_for_divider(uint8_t clock_divider) +{ + return 1.0 / (APB_CLK_FREQ / clock_divider); +} + // Return the resolution (shortest possible pulse), in seconds STATIC mp_obj_t esp32_rmt_resolution(mp_obj_t self_in) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(1.0/(APB_CLK_FREQ / self->clock_divider)); + return mp_obj_new_float(resolution_for_divider(self->clock_divider)); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_resolution_obj, esp32_rmt_resolution); @@ -100,7 +137,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_resolution_obj, esp32_rmt_resolution) // Return the longest pulse duration STATIC mp_obj_t esp32_rmt_max_duration(mp_obj_t self_in) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float((1.0/(APB_CLK_FREQ / self->clock_divider)) * 32768); + return mp_obj_new_float(resolution_for_divider(self->clock_divider) * 32768); // 2^15 is the maximum number of bits for ticks } STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_max_duration_obj, esp32_rmt_max_duration); @@ -122,19 +159,19 @@ STATIC mp_obj_t esp32_rmt_send_pulses(mp_obj_t self_in, mp_obj_t pulses, mp_obj_ mp_uint_t num_items = (pulses_length / 2) + (pulses_length % 2); rmt_item32_t* items = (rmt_item32_t*)m_malloc(num_items * sizeof(rmt_item32_t)); - printf("num_items=%d pulses_length=%d\n\n", num_items, pulses_length); + //printf("num_items=%d pulses_length=%d\n\n", num_items, pulses_length); for (mp_uint_t item_index = 0; item_index < num_items; item_index++) { mp_uint_t pulse_index = item_index * 2; items[item_index].duration0 = mp_obj_get_int(pulses_ptr[pulse_index++]); items[item_index].level0 = level++; // Note that level _could_ wrap. - printf("duration=%d level=%d\n", items[item_index].duration0, items[item_index].level0); + //printf("duration=%d level=%d\n", items[item_index].duration0, items[item_index].level0); if (pulse_index < pulses_length) { items[item_index].duration1 = mp_obj_get_int(pulses_ptr[pulse_index]); items[item_index].level1 = level++; - printf("duration=%d level=%d\n", items[item_index].duration1, items[item_index].level1); + //printf("duration=%d level=%d\n", items[item_index].duration1, items[item_index].level1); } } ESP_ERROR_CHECK(rmt_write_items(self->channel_id, items, num_items, true)); @@ -152,6 +189,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_rmt_send_pulses_obj, esp32_rmt_send_pulse STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_send_pulses), MP_ROM_PTR(&esp32_rmt_send_pulses_obj) }, + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_resolution), MP_ROM_PTR(&esp32_rmt_resolution_obj) }, { MP_ROM_QSTR(MP_QSTR_max_duration), MP_ROM_PTR(&esp32_rmt_max_duration_obj) }, }; @@ -164,6 +202,3 @@ const mp_obj_type_t esp32_rmt_type = { .make_new = esp32_rmt_make_new, .locals_dict = (mp_obj_dict_t *)&esp32_rmt_locals_dict, }; - -// ch1 = RMT(1, Pin(18), 80, 0) # channel, pin, clock_divider, idle_level -// ch1.send_items((1000, 500, 1000), start=0) \ No newline at end of file From e7bb36f99cef33d7c9c994a8c2c0d6dc96094345 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Thu, 26 Sep 2019 14:58:38 +1000 Subject: [PATCH 03/20] Allow lists or tuple to be used to define tuples - throw exception if other type detected. --- ports/esp32/esp32_rmt.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index e375d1fd372ec..224f21f375f0a 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -45,11 +45,14 @@ typedef struct _esp32_rmt_obj_t { // TODO // +// o Write up documentation // o Check all the input parameters // o Return exceptions when errors occur // o Manage an array of channels (so user can't create the same one repeatedly) // - Check if channel is initialised (in particular: after deinit) // o Investigate: Send without blocking? (Need an 'is_sending' method) +// - Memory management becomes significantly more complex. Need to ensure memory is not freed before it's used. +// - Also need to take care that memory is not freed by the MicroPython GC. // o Add debug option? Emit printf debug messages? // - Idea: Generate timing diagram (maybe with https://wavedrom.com/ ?) Extension. // o Consider: Auto-manage RMT channels? Currently user chooses a channel @@ -65,6 +68,8 @@ typedef struct _esp32_rmt_obj_t { // o Add carrier modulation methods // o Add rx support +// ESP-IDF RMT: +// Espressif examples: https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/RMT STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { // fixme: check arguments @@ -154,6 +159,10 @@ STATIC mp_obj_t esp32_rmt_send_pulses(mp_obj_t self_in, mp_obj_t pulses, mp_obj_ // fixme: Handle if not a tuple if(MP_OBJ_IS_TYPE(pulses, &mp_type_tuple) == true) { mp_obj_tuple_get(pulses, &pulses_length, &pulses_ptr); + } else if(MP_OBJ_IS_TYPE(pulses, &mp_type_list) == true) { + mp_obj_list_get(pulses, &pulses_length, &pulses_ptr); + } else { + mp_raise_TypeError("Pulses must be defined as a tuple or list"); } mp_uint_t num_items = (pulses_length / 2) + (pulses_length % 2); From 56ff9e1e6526178037b08c5e0b5ea3980865f904 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Sun, 29 Sep 2019 11:10:56 +1000 Subject: [PATCH 04/20] Improved some error handling --- ports/esp32/esp32_rmt.c | 32 +++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 224f21f375f0a..c08815e12fc85 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -43,6 +43,16 @@ typedef struct _esp32_rmt_obj_t { } esp32_rmt_obj_t; + +// Defined in machine_time.c; simply added the error message +STATIC esp_err_t check_esp_err(esp_err_t code) { + if (code) { + mp_raise_msg(&mp_type_OSError, esp_err_to_name(code)); + } + + return code; +} + // TODO // // o Write up documentation @@ -72,15 +82,17 @@ typedef struct _esp32_rmt_obj_t { // Espressif examples: https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/RMT STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - // fixme: check arguments - // 1 <= channel_id <= 8 - // 1 <= clock_divider <= 255 + // 0 <= channel_id <= 7 mp_arg_check_num(n_args, n_kw, 3, MP_OBJ_FUN_ARGS_MAX, true); mp_uint_t channel_id = mp_obj_get_int(args[0]); gpio_num_t pin_id = machine_pin_get_id(args[1]); mp_uint_t clock_divider = mp_obj_get_int(args[2]); // fixme: should have an idle_level... + if (clock_divider < 1 || clock_divider > 255) { + mp_raise_ValueError("Clock divider must be between 1 and 255"); + } + esp32_rmt_obj_t *self = m_new_obj(esp32_rmt_obj_t); self->base.type = &esp32_rmt_type; self->channel_id = channel_id; @@ -103,9 +115,8 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz config.clk_div = self->clock_divider; - // fixme: Error handling should be nicer; raise an exception - ESP_ERROR_CHECK(rmt_config(&config)); - ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0)); + check_esp_err(rmt_config(&config)); + check_esp_err(rmt_driver_install(config.channel, 0, 0)); return MP_OBJ_FROM_PTR(self); } @@ -126,6 +137,7 @@ STATIC mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); + static float resolution_for_divider(uint8_t clock_divider) { return 1.0 / (APB_CLK_FREQ / clock_divider); @@ -151,18 +163,20 @@ STATIC mp_obj_t esp32_rmt_send_pulses(mp_obj_t self_in, mp_obj_t pulses, mp_obj_ esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_uint_t level = mp_obj_get_int(start_level); // fixme: Check inputs - // start_level => [0,1] + // start_level should be a kwargs + if (level != 0 && level != 1) { + mp_raise_ValueError("Start level can only be 0 or 1"); + } mp_uint_t pulses_length = 0; mp_obj_t* pulses_ptr = NULL; - // fixme: Handle if not a tuple if(MP_OBJ_IS_TYPE(pulses, &mp_type_tuple) == true) { mp_obj_tuple_get(pulses, &pulses_length, &pulses_ptr); } else if(MP_OBJ_IS_TYPE(pulses, &mp_type_list) == true) { mp_obj_list_get(pulses, &pulses_length, &pulses_ptr); } else { - mp_raise_TypeError("Pulses must be defined as a tuple or list"); + mp_raise_TypeError("Pulses must be specified with a tuple or list"); } mp_uint_t num_items = (pulses_length / 2) + (pulses_length % 2); From 5baff6812a6473f4c0bfd063cca7d0ac11260382 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Sun, 29 Sep 2019 16:26:55 +1000 Subject: [PATCH 05/20] Make start_level an optional keyword argument. --- ports/esp32/esp32_rmt.c | 101 +++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 53 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index c08815e12fc85..7c418aa8198db 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -32,6 +32,23 @@ #include "modmachine.h" #include "mphalport.h" + +// This exposes the ESP32's RMT module to MicroPython. RMT is provided by the Espressif ESP-IDF: +// +// https://docs.espressif.com/projects/esp-idf/en/latest/api-reference/peripherals/rmt.html +// +// With some examples provided: +// +// https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/RMT +// +// RMT allows accurate (down to 12.5ns resolution) transmit - and receive - of pulse signals. +// Originally designed to generate infrared remote control signals, the module is very +// flexible and quite easy-to-use. +// +// This current MicroPython implementation lacks some major features, notably receive pulses +// and carrier output. + + // Forward declaration extern const mp_obj_type_t esp32_rmt_type; @@ -53,41 +70,12 @@ STATIC esp_err_t check_esp_err(esp_err_t code) { return code; } -// TODO -// -// o Write up documentation -// o Check all the input parameters -// o Return exceptions when errors occur -// o Manage an array of channels (so user can't create the same one repeatedly) -// - Check if channel is initialised (in particular: after deinit) -// o Investigate: Send without blocking? (Need an 'is_sending' method) -// - Memory management becomes significantly more complex. Need to ensure memory is not freed before it's used. -// - Also need to take care that memory is not freed by the MicroPython GC. -// o Add debug option? Emit printf debug messages? -// - Idea: Generate timing diagram (maybe with https://wavedrom.com/ ?) Extension. -// o Consider: Auto-manage RMT channels? Currently user chooses a channel -// - Alternative: Give the user the next available channel -// o Raise exception if pulses is not a tuple -// - Consider: Using iterables. Will make memory management much trickier though -// - Consider: Allowing lists as well as tuples -// o Do we need to allow the option to use multiple memory blocks? -// o Consider supporting other ways to specify pulses: -// - Fixed interval (specify pattern of 1's/0's) -// - Specify both value and ticks -// - See PyCom RMT docs -// o Add carrier modulation methods -// o Add rx support - -// ESP-IDF RMT: -// Espressif examples: https://github.com/espressif/arduino-esp32/tree/master/libraries/ESP32/examples/RMT STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - // 0 <= channel_id <= 7 mp_arg_check_num(n_args, n_kw, 3, MP_OBJ_FUN_ARGS_MAX, true); mp_uint_t channel_id = mp_obj_get_int(args[0]); gpio_num_t pin_id = machine_pin_get_id(args[1]); mp_uint_t clock_divider = mp_obj_get_int(args[2]); - // fixme: should have an idle_level... if (clock_divider < 1 || clock_divider > 255) { mp_raise_ValueError("Clock divider must be between 1 and 255"); @@ -137,7 +125,7 @@ STATIC mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); - +// Helper function to return the resolution for the specified clock divider static float resolution_for_divider(uint8_t clock_divider) { return 1.0 / (APB_CLK_FREQ / clock_divider); @@ -151,21 +139,32 @@ STATIC mp_obj_t esp32_rmt_resolution(mp_obj_t self_in) { STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_resolution_obj, esp32_rmt_resolution); -// Return the longest pulse duration -STATIC mp_obj_t esp32_rmt_max_duration(mp_obj_t self_in) { +// Return the longest pulse duration, in seconds. +// Increasing the clock_divider yields longer maximum pulse lengths. +STATIC mp_obj_t esp32_rmt_max_pulse_length(mp_obj_t self_in) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); return mp_obj_new_float(resolution_for_divider(self->clock_divider) * 32768); // 2^15 is the maximum number of bits for ticks } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_max_duration_obj, esp32_rmt_max_duration); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_max_pulse_length_obj, esp32_rmt_max_pulse_length); -STATIC mp_obj_t esp32_rmt_send_pulses(mp_obj_t self_in, mp_obj_t pulses, mp_obj_t start_level) { - esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_uint_t level = mp_obj_get_int(start_level); - // fixme: Check inputs - // start_level should be a kwargs - if (level != 0 && level != 1) { - mp_raise_ValueError("Start level can only be 0 or 1"); +STATIC mp_obj_t esp32_rmt_send_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_level, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + + 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_level = args[2].u_int; + + if (start_level < 0 || start_level > 1) { + mp_raise_ValueError("start_level can only be 0 or 1"); } mp_uint_t pulses_length = 0; @@ -188,33 +187,29 @@ STATIC mp_obj_t esp32_rmt_send_pulses(mp_obj_t self_in, mp_obj_t pulses, mp_obj_ { mp_uint_t pulse_index = item_index * 2; items[item_index].duration0 = mp_obj_get_int(pulses_ptr[pulse_index++]); - items[item_index].level0 = level++; // Note that level _could_ wrap. - //printf("duration=%d level=%d\n", items[item_index].duration0, items[item_index].level0); + items[item_index].level0 = start_level++; // Note that start_level _could_ wrap. + //printf("duration=%d start_level=%d\n", items[item_index].duration0, items[item_index].level0); if (pulse_index < pulses_length) { items[item_index].duration1 = mp_obj_get_int(pulses_ptr[pulse_index]); - items[item_index].level1 = level++; - //printf("duration=%d level=%d\n", items[item_index].duration1, items[item_index].level1); + items[item_index].level1 = start_level++; + //printf("duration=%d start_level=%d\n", items[item_index].duration1, items[item_index].level1); } } - ESP_ERROR_CHECK(rmt_write_items(self->channel_id, items, num_items, true)); + check_esp_err(rmt_write_items(self->channel_id, items, num_items, true)); - m_free(items); + m_free(items); // Not freed if there's an error in rmt_write_item() - // if (result != ESP_OK) { - // nlr_raise(mp_obj_new_exception_msg(&mp_type_OSError, "rmt_write_items failed!")); - // } - - return MP_OBJ_NEW_SMALL_INT(num_items); + return mp_const_none; } -STATIC MP_DEFINE_CONST_FUN_OBJ_3(esp32_rmt_send_pulses_obj, esp32_rmt_send_pulses); +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_send_pulses_obj, 2, esp32_rmt_send_pulses); STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_send_pulses), MP_ROM_PTR(&esp32_rmt_send_pulses_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_resolution), MP_ROM_PTR(&esp32_rmt_resolution_obj) }, - { MP_ROM_QSTR(MP_QSTR_max_duration), MP_ROM_PTR(&esp32_rmt_max_duration_obj) }, + { MP_ROM_QSTR(MP_QSTR_max_duration), MP_ROM_PTR(&esp32_rmt_max_pulse_length_obj) }, }; STATIC MP_DEFINE_CONST_DICT(esp32_rmt_locals_dict, esp32_rmt_locals_dict_table); From 7069370481fe653a9b04cb1c0ae44f74933f30f6 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Sun, 29 Sep 2019 23:36:22 +1000 Subject: [PATCH 06/20] The start of documentation for RMT --- docs/esp32/general.rst | 1 + docs/esp32/quickref.rst | 16 ++++++++++++++++ 2 files changed, 17 insertions(+) diff --git a/docs/esp32/general.rst b/docs/esp32/general.rst index 51918d4e183ea..3894eccc555bf 100644 --- a/docs/esp32/general.rst +++ b/docs/esp32/general.rst @@ -52,6 +52,7 @@ For your convenience, some of technical specifications are provided below: * I2S: 2 * ADC: 12-bit SAR ADC up to 18 channels * DAC: 2 8-bit DACs +* RMT * Programming: using BootROM bootloader from UART - due to external FlashROM and always-available BootROM bootloader, the ESP32 is not brickable diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 20e728d1c7d89..fcacb72f5ea55 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -365,6 +365,22 @@ Notes: p1 = Pin(4, Pin.OUT, None) +RMT +--- + +The RMT is a module, specific to the ESP32, that allows pulses of very accurate interval +and duration to be sent and received:: + + rmt_channel = esp32.RMT(0, machine.Pin(18), 100) # Channel 0, bound to Pin 18, clock divider of 100 + rmt_channel.send_pulses((10, 1000, 10, 2000, 10, 3000)) # Send three long pulses with short delays between + +For more details see Espressif `ESP-IDF RMT documentation. +`_. + +.. Warning:: + The current MicroPython RMT implementation lacks some features, notably receiving pulses + and carrier transmit. + OneWire driver -------------- From 966cde8dd8f0f2e5b0c2e788ed8aaf3f0596c5a4 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Mon, 7 Oct 2019 21:48:31 +1100 Subject: [PATCH 07/20] Updated quickref, incorporating mcauser's suggestion. Also updated quickref example code. --- docs/esp32/quickref.rst | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index fcacb72f5ea55..67e7df21e36fe 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -368,11 +368,15 @@ Notes: RMT --- -The RMT is a module, specific to the ESP32, that allows pulses of very accurate interval -and duration to be sent and received:: - - rmt_channel = esp32.RMT(0, machine.Pin(18), 100) # Channel 0, bound to Pin 18, clock divider of 100 - rmt_channel.send_pulses((10, 1000, 10, 2000, 10, 3000)) # Send three long pulses with short delays between +The RMT (Remote Control) module, specific to the ESP32, was originally designed to send +and receive infrared remote control signals. However, due to a flexible design and +very accurate (12.5ns) pulse generation, it can also be used to transmit or receive +many other types of digital signals:: + + r = esp32.RMT(0, Pin(18), 80) + r # RMT(channel=0, pin=18, clock_divider=80) + r.resolution(), r.max_pulse_length() # (1e-06, 0.032768) + r.send_pulses((100, 2000, 100, 4000), start_level=0) # Send 0 for 100*1e-06s, 1 for 2000*1e-06s etc For more details see Espressif `ESP-IDF RMT documentation. `_. From e6ebbdc18bba8adb868023b4fccf6dd58ed17346 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Wed, 9 Oct 2019 12:52:15 +1100 Subject: [PATCH 08/20] Set the idle output level of the RMT channel to zero. --- ports/esp32/esp32_rmt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 7c418aa8198db..18f7ebb8ef37e 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -95,7 +95,7 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz config.tx_config.loop_en = 0; config.tx_config.carrier_en = 0; - config.tx_config.idle_output_en = 0; + config.tx_config.idle_output_en = 1; config.tx_config.idle_level = 0; config.tx_config.carrier_duty_percent = 0; config.tx_config.carrier_freq_hz = 0; From 3ac8659f37c8774f5588a684264e18154c436bf9 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Wed, 13 Nov 2019 14:22:43 +1100 Subject: [PATCH 09/20] Handle deinit correctly - when calling deinit on an RMT object and when soft resets occur. --- ports/esp32/esp32_rmt.c | 23 ++++++++++++++++++++--- ports/esp32/main.c | 5 +++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 18f7ebb8ef37e..511923f545cfd 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -62,6 +62,8 @@ typedef struct _esp32_rmt_obj_t { // Defined in machine_time.c; simply added the error message +// Fixme: Should use this updated error hadline more widely in the ESP32 port. +// At least update the method in machine_time.c. STATIC esp_err_t check_esp_err(esp_err_t code) { if (code) { mp_raise_msg(&mp_type_OSError, esp_err_to_name(code)); @@ -112,7 +114,11 @@ 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); - mp_printf(print, "RMT(channel=%u, pin=%u, clock_divider=%u)", self->channel_id, self->pin, self->clock_divider); + if (self->pin != -1) { + mp_printf(print, "RMT(channel=%u, pin=%u, clock_divider=%u)", self->channel_id, self->pin, self->clock_divider); + } else { + mp_printf(print, "RMT(channel=%u, inactive)", self->channel_id); + } } @@ -121,13 +127,14 @@ STATIC mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); rmt_driver_uninstall(self->channel_id); + self->pin = -1; // -1 to indicate RMT is unused + return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); // Helper function to return the resolution for the specified clock divider -static float resolution_for_divider(uint8_t clock_divider) -{ +static float resolution_for_divider(uint8_t clock_divider) { return 1.0 / (APB_CLK_FREQ / clock_divider); } @@ -204,6 +211,16 @@ STATIC mp_obj_t esp32_rmt_send_pulses(size_t n_args, const mp_obj_t *pos_args, m } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_send_pulses_obj, 2, esp32_rmt_send_pulses); +void esp32_rmt_deinit_all(void) { + rmt_channel_status_result_t status[RMT_CHANNEL_MAX]; + rmt_get_channel_status(status); + + for (size_t i = 0; i < RMT_CHANNEL_MAX; i++) { + if (status[i].status != RMT_CHANNEL_UNINIT) { + rmt_driver_uninstall(i); + } + } +} STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_send_pulses), MP_ROM_PTR(&esp32_rmt_send_pulses_obj) }, diff --git a/ports/esp32/main.c b/ports/esp32/main.c index b0d1b1537065d..948005aa34056 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -57,6 +57,7 @@ #include "modnetwork.h" #include "mpthreadport.h" + // MicroPython runs as a task under FreeRTOS #define MP_TASK_PRIORITY (ESP_TASK_PRIO_MIN + 1) #define MP_TASK_STACK_SIZE (16 * 1024) @@ -67,6 +68,9 @@ int vprintf_null(const char *format, va_list ap) { return 0; } +// Forward-declare RMT deinit function (could be defined in a header). +void esp32_rmt_deinit_all(void); + void mp_task(void *pvParameter) { volatile uint32_t sp = (uint32_t)get_sp(); #if MICROPY_PY_THREAD @@ -146,6 +150,7 @@ void mp_task(void *pvParameter) { mp_hal_stdout_tx_str("MPY: soft reboot\r\n"); // deinitialise peripherals + esp32_rmt_deinit_all(); machine_pins_deinit(); usocket_events_deinit(); From d6bc920d50a41e03da26a412b66fa3962fcf91ad Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Tue, 26 Nov 2019 12:30:29 +1100 Subject: [PATCH 10/20] RMT.send_pulses is now non-blocking. Also fewer memory allocations, since internal buffer is reused (and reallocated if necessary). Updated the way resources are freed - now simpler and more robust. --- ports/esp32/esp32_rmt.c | 53 +++++++++++++++++++---------------------- ports/esp32/main.c | 4 ---- 2 files changed, 25 insertions(+), 32 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 511923f545cfd..665572b2931cb 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -57,6 +57,8 @@ typedef struct _esp32_rmt_obj_t { uint8_t channel_id; gpio_num_t pin; uint8_t clock_divider; + mp_uint_t num_items; + rmt_item32_t* items; } esp32_rmt_obj_t; @@ -83,7 +85,7 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_raise_ValueError("Clock divider must be between 1 and 255"); } - esp32_rmt_obj_t *self = m_new_obj(esp32_rmt_obj_t); + 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; @@ -117,7 +119,7 @@ STATIC void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ if (self->pin != -1) { mp_printf(print, "RMT(channel=%u, pin=%u, clock_divider=%u)", self->channel_id, self->pin, self->clock_divider); } else { - mp_printf(print, "RMT(channel=%u, inactive)", self->channel_id); + mp_printf(print, "RMT()"); } } @@ -125,10 +127,13 @@ STATIC void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ STATIC mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { // fixme: check for valid channel. Return exception if error occurs. esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - rmt_driver_uninstall(self->channel_id); - - self->pin = -1; // -1 to indicate RMT is unused + if (self->pin != -1) // Check if channel has already been deinitialised. + { + rmt_driver_uninstall(self->channel_id); + self->pin = -1; // -1 to indicate RMT is unused + m_free(self->items); + } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); @@ -158,9 +163,9 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_max_pulse_length_obj, esp32_rmt_max_p STATIC mp_obj_t esp32_rmt_send_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_level, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + { 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_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; @@ -186,45 +191,37 @@ STATIC mp_obj_t esp32_rmt_send_pulses(size_t n_args, const mp_obj_t *pos_args, m } mp_uint_t num_items = (pulses_length / 2) + (pulses_length % 2); - rmt_item32_t* items = (rmt_item32_t*)m_malloc(num_items * sizeof(rmt_item32_t)); - + if (num_items > self->num_items) + { + //printf("realloc required, old=%u, new=%u\n", self->num_items, num_items); + self->items = (rmt_item32_t*)m_realloc(self->items, num_items * sizeof(rmt_item32_t *)); + self->num_items = num_items; + } //printf("num_items=%d pulses_length=%d\n\n", num_items, pulses_length); for (mp_uint_t item_index = 0; item_index < num_items; item_index++) { mp_uint_t pulse_index = item_index * 2; - items[item_index].duration0 = mp_obj_get_int(pulses_ptr[pulse_index++]); - items[item_index].level0 = start_level++; // Note that start_level _could_ wrap. + self->items[item_index].duration0 = mp_obj_get_int(pulses_ptr[pulse_index++]); + self->items[item_index].level0 = start_level++; // Note that start_level _could_ wrap. //printf("duration=%d start_level=%d\n", items[item_index].duration0, items[item_index].level0); if (pulse_index < pulses_length) { - items[item_index].duration1 = mp_obj_get_int(pulses_ptr[pulse_index]); - items[item_index].level1 = start_level++; + self->items[item_index].duration1 = mp_obj_get_int(pulses_ptr[pulse_index]); + self->items[item_index].level1 = start_level++; //printf("duration=%d start_level=%d\n", items[item_index].duration1, items[item_index].level1); } } - check_esp_err(rmt_write_items(self->channel_id, items, num_items, true)); - - m_free(items); // Not freed if there's an error in rmt_write_item() + check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false /* non-blocking */)); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_send_pulses_obj, 2, esp32_rmt_send_pulses); -void esp32_rmt_deinit_all(void) { - rmt_channel_status_result_t status[RMT_CHANNEL_MAX]; - rmt_get_channel_status(status); - - for (size_t i = 0; i < RMT_CHANNEL_MAX; i++) { - if (status[i].status != RMT_CHANNEL_UNINIT) { - rmt_driver_uninstall(i); - } - } -} - STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_send_pulses), MP_ROM_PTR(&esp32_rmt_send_pulses_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_resolution), MP_ROM_PTR(&esp32_rmt_resolution_obj) }, { MP_ROM_QSTR(MP_QSTR_max_duration), MP_ROM_PTR(&esp32_rmt_max_pulse_length_obj) }, }; diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 948005aa34056..91d9d35d551d6 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -68,9 +68,6 @@ int vprintf_null(const char *format, va_list ap) { return 0; } -// Forward-declare RMT deinit function (could be defined in a header). -void esp32_rmt_deinit_all(void); - void mp_task(void *pvParameter) { volatile uint32_t sp = (uint32_t)get_sp(); #if MICROPY_PY_THREAD @@ -150,7 +147,6 @@ void mp_task(void *pvParameter) { mp_hal_stdout_tx_str("MPY: soft reboot\r\n"); // deinitialise peripherals - esp32_rmt_deinit_all(); machine_pins_deinit(); usocket_events_deinit(); From b3a151d565268ca2b955f8200b21cecd15b85cc2 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Thu, 28 Nov 2019 15:43:04 +1100 Subject: [PATCH 11/20] Remove remnants of start_level to start rename. --- ports/esp32/esp32_rmt.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 665572b2931cb..e88979533d1fd 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -173,10 +173,10 @@ STATIC mp_obj_t esp32_rmt_send_pulses(size_t n_args, const mp_obj_t *pos_args, m 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_level = args[2].u_int; + mp_uint_t start = args[2].u_int; - if (start_level < 0 || start_level > 1) { - mp_raise_ValueError("start_level can only be 0 or 1"); + if (start < 0 || start > 1) { + mp_raise_ValueError("start can only be 0 or 1"); } mp_uint_t pulses_length = 0; @@ -203,13 +203,13 @@ STATIC mp_obj_t esp32_rmt_send_pulses(size_t n_args, const mp_obj_t *pos_args, m { 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_level++; // Note that start_level _could_ wrap. - //printf("duration=%d start_level=%d\n", items[item_index].duration0, items[item_index].level0); + self->items[item_index].level0 = start++; // Note that start _could_ wrap. + //printf("duration=%d start=%d\n", items[item_index].duration0, items[item_index].level0); if (pulse_index < pulses_length) { self->items[item_index].duration1 = mp_obj_get_int(pulses_ptr[pulse_index]); - self->items[item_index].level1 = start_level++; - //printf("duration=%d start_level=%d\n", items[item_index].duration1, items[item_index].level1); + self->items[item_index].level1 = start++; + //printf("duration=%d start=%d\n", items[item_index].duration1, items[item_index].level1); } } check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false /* non-blocking */)); From a0ffb5f200deef209b710e54837bd87961b891e7 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Thu, 5 Dec 2019 17:22:42 +1100 Subject: [PATCH 12/20] Tidy up the ESP32 RMT docs - a clearer description in the quickref --- docs/esp32/quickref.rst | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 67e7df21e36fe..b1500abbafdbb 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -368,21 +368,35 @@ Notes: RMT --- -The RMT (Remote Control) module, specific to the ESP32, was originally designed to send -and receive infrared remote control signals. However, due to a flexible design and -very accurate (12.5ns) pulse generation, it can also be used to transmit or receive -many other types of digital signals:: +The RMT (Remote Control) module, specific to the ESP32, was originally designed +to send and receive infrared remote control signals. However, due to a flexible +design and very accurate (as low as 12.5ns) pulse generation, it can also be +used to transmit or receive many other types of digital signals:: r = esp32.RMT(0, Pin(18), 80) r # RMT(channel=0, pin=18, clock_divider=80) r.resolution(), r.max_pulse_length() # (1e-06, 0.032768) - r.send_pulses((100, 2000, 100, 4000), start_level=0) # Send 0 for 100*1e-06s, 1 for 2000*1e-06s etc + r.send_pulses((100, 2000, 100, 4000), start=0) # Send 0 for 100*1e-06s, 1 for 2000*1e-06s etc -For more details see Espressif `ESP-IDF RMT documentation. +The input to the RMT module is an 80MHz clock. ``clock_divider`` *divides* the +clock input which determines the minimum *resolution* of a pulse. The numbers +specificed in ``send_pulses`` are *multiplied* by the minimum resolution to +define the pulses. + +``clock_divider`` is an 8-bit divider (0-255) and each pulse can be defined by +multiplying the minimum resolution by a 15-bit (0-32,768) number. There are +eight channels (0-7) and each can have a different clock divider. + +So, in the example above, the 80MHz clock is divided by 80. Thus the minimum +resolution is (1/(80Mhz/80)) 1µs. Since the ``start`` level is 0 and toggles +with each number, the bitstream is ``0101`` with durations of [100µs, 2ms, +100µs, 4ms]. + +For more details see Espressif's `ESP-IDF RMT documentation. `_. .. Warning:: - The current MicroPython RMT implementation lacks some features, notably receiving pulses + The current MicroPython RMT implementation lacks some features, most notably receiving pulses and carrier transmit. OneWire driver From 78998dec8cae88e8548f5e7691ff38a8a7c48125 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Fri, 6 Dec 2019 13:10:28 +1100 Subject: [PATCH 13/20] Add wait_done to allow user to query whether pulses are currently being transmitted --- ports/esp32/esp32_rmt.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index e88979533d1fd..bfdefc49b6b13 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -138,11 +138,13 @@ STATIC mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); + // Helper function to return the resolution for the specified clock divider static float resolution_for_divider(uint8_t clock_divider) { return 1.0 / (APB_CLK_FREQ / clock_divider); } + // Return the resolution (shortest possible pulse), in seconds STATIC mp_obj_t esp32_rmt_resolution(mp_obj_t self_in) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); @@ -151,6 +153,26 @@ STATIC mp_obj_t esp32_rmt_resolution(mp_obj_t self_in) { STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_resolution_obj, esp32_rmt_resolution); +// Query whether the channel has finished sending pulses. Takes an optional +// timeout (in ticks of the 80MHz clock), returning true if the pulse stream has +// completed or false if they are still transmitting (or timeout is reached). +STATIC mp_obj_t esp32_rmt_wait_done(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_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} }, + }; + + 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); + + esp_err_t err = rmt_wait_tx_done(self->channel_id, args[1].u_int); + return err == ESP_OK ? mp_const_true : mp_const_false; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_wait_done_obj, 1, esp32_rmt_wait_done); + + // Return the longest pulse duration, in seconds. // Increasing the clock_divider yields longer maximum pulse lengths. STATIC mp_obj_t esp32_rmt_max_pulse_length(mp_obj_t self_in) { @@ -218,15 +240,18 @@ STATIC mp_obj_t esp32_rmt_send_pulses(size_t n_args, const mp_obj_t *pos_args, m } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_send_pulses_obj, 2, esp32_rmt_send_pulses); + STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_send_pulses), MP_ROM_PTR(&esp32_rmt_send_pulses_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_resolution), MP_ROM_PTR(&esp32_rmt_resolution_obj) }, + { MP_ROM_QSTR(MP_QSTR_wait_done), MP_ROM_PTR(&esp32_rmt_wait_done_obj) }, { MP_ROM_QSTR(MP_QSTR_max_duration), MP_ROM_PTR(&esp32_rmt_max_pulse_length_obj) }, }; STATIC MP_DEFINE_CONST_DICT(esp32_rmt_locals_dict, esp32_rmt_locals_dict_table); + const mp_obj_type_t esp32_rmt_type = { { &mp_type_type }, .name = MP_QSTR_RMT, From fd7f6ad7b5ca085d478c116b5c67ff379602ca71 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Fri, 6 Dec 2019 13:36:09 +1100 Subject: [PATCH 14/20] Added a simplistic way to enabled loop tx --- ports/esp32/esp32_rmt.c | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index bfdefc49b6b13..9cb6785542708 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -153,6 +153,15 @@ STATIC mp_obj_t esp32_rmt_resolution(mp_obj_t self_in) { STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_resolution_obj, esp32_rmt_resolution); +// Return the longest pulse duration, in seconds. +// Increasing the clock_divider yields longer maximum pulse lengths. +STATIC mp_obj_t esp32_rmt_max_pulse_length(mp_obj_t self_in) { + esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); + return mp_obj_new_float(resolution_for_divider(self->clock_divider) * 32768); // 2^15 is the maximum number of bits for ticks +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_max_pulse_length_obj, esp32_rmt_max_pulse_length); + + // Query whether the channel has finished sending pulses. Takes an optional // timeout (in ticks of the 80MHz clock), returning true if the pulse stream has // completed or false if they are still transmitting (or timeout is reached). @@ -173,13 +182,14 @@ STATIC mp_obj_t esp32_rmt_wait_done(size_t n_args, const mp_obj_t *pos_args, mp_ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_wait_done_obj, 1, esp32_rmt_wait_done); -// Return the longest pulse duration, in seconds. -// Increasing the clock_divider yields longer maximum pulse lengths. -STATIC mp_obj_t esp32_rmt_max_pulse_length(mp_obj_t self_in) { +STATIC mp_obj_t esp32_rmt_loop(mp_obj_t self_in, mp_obj_t loop) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(resolution_for_divider(self->clock_divider) * 32768); // 2^15 is the maximum number of bits for ticks + + check_esp_err(rmt_set_tx_loop_mode(self->channel_id, mp_obj_get_int(loop))); + return mp_const_none; + } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_max_pulse_length_obj, esp32_rmt_max_pulse_length); +STATIC MP_DEFINE_CONST_FUN_OBJ_2(esp32_rmt_loop_obj, esp32_rmt_loop); STATIC mp_obj_t esp32_rmt_send_pulses(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -242,12 +252,13 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_send_pulses_obj, 2, esp32_rmt_send_p STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { - { MP_ROM_QSTR(MP_QSTR_send_pulses), MP_ROM_PTR(&esp32_rmt_send_pulses_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR_resolution), MP_ROM_PTR(&esp32_rmt_resolution_obj) }, - { MP_ROM_QSTR(MP_QSTR_wait_done), MP_ROM_PTR(&esp32_rmt_wait_done_obj) }, { MP_ROM_QSTR(MP_QSTR_max_duration), MP_ROM_PTR(&esp32_rmt_max_pulse_length_obj) }, + { MP_ROM_QSTR(MP_QSTR_wait_done), MP_ROM_PTR(&esp32_rmt_wait_done_obj) }, + { MP_ROM_QSTR(MP_QSTR_loop), MP_ROM_PTR(&esp32_rmt_loop_obj) }, + { MP_ROM_QSTR(MP_QSTR_send_pulses), MP_ROM_PTR(&esp32_rmt_send_pulses_obj) }, }; STATIC MP_DEFINE_CONST_DICT(esp32_rmt_locals_dict, esp32_rmt_locals_dict_table); From 06e349709e7bc3c2a287c5cda6da38340eb34c6a Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Sun, 15 Dec 2019 10:39:23 +1100 Subject: [PATCH 15/20] Provide accessors for the clock divider and source clock. Remove resolution and maximum pulse length which can be inferred. --- ports/esp32/esp32_rmt.c | 35 +++++++++++++++-------------------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 9cb6785542708..7eb40c6e6b32f 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -117,7 +117,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, clock_divider=%u)", self->channel_id, self->pin, self->clock_divider); + mp_printf(print, "RMT(channel=%u, pin=%u, source_freq=%u, clock_div=%u)", + self->channel_id, self->pin, APB_CLK_FREQ, self->clock_divider); } else { mp_printf(print, "RMT()"); } @@ -139,27 +140,21 @@ STATIC mp_obj_t esp32_rmt_deinit(mp_obj_t self_in) { STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_deinit_obj, esp32_rmt_deinit); -// Helper function to return the resolution for the specified clock divider -static float resolution_for_divider(uint8_t clock_divider) { - return 1.0 / (APB_CLK_FREQ / clock_divider); +// Return the source frequency. +// Currently only the APB clock (80MHz) can be used but it is possible other +// clock sources will added in the future. +STATIC mp_obj_t esp32_rmt_source_freq(mp_obj_t self_in) { + return mp_obj_new_int(APB_CLK_FREQ); } +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_source_freq_obj, esp32_rmt_source_freq); -// Return the resolution (shortest possible pulse), in seconds -STATIC mp_obj_t esp32_rmt_resolution(mp_obj_t self_in) { +// Return the clock divider. +STATIC mp_obj_t esp32_rmt_clock_div(mp_obj_t self_in) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(resolution_for_divider(self->clock_divider)); + return mp_obj_new_int(self->clock_divider); } -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_resolution_obj, esp32_rmt_resolution); - - -// Return the longest pulse duration, in seconds. -// Increasing the clock_divider yields longer maximum pulse lengths. -STATIC mp_obj_t esp32_rmt_max_pulse_length(mp_obj_t self_in) { - esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_float(resolution_for_divider(self->clock_divider) * 32768); // 2^15 is the maximum number of bits for ticks -} -STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_max_pulse_length_obj, esp32_rmt_max_pulse_length); +STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_clock_div_obj, esp32_rmt_clock_div); // Query whether the channel has finished sending pulses. Takes an optional @@ -208,7 +203,7 @@ STATIC mp_obj_t esp32_rmt_send_pulses(size_t n_args, const mp_obj_t *pos_args, m mp_uint_t start = args[2].u_int; if (start < 0 || start > 1) { - mp_raise_ValueError("start can only be 0 or 1"); + mp_raise_ValueError("Start must be 0 or 1"); } mp_uint_t pulses_length = 0; @@ -254,8 +249,8 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(esp32_rmt_send_pulses_obj, 2, esp32_rmt_send_p STATIC const mp_rom_map_elem_t esp32_rmt_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&esp32_rmt_deinit_obj) }, - { MP_ROM_QSTR(MP_QSTR_resolution), MP_ROM_PTR(&esp32_rmt_resolution_obj) }, - { MP_ROM_QSTR(MP_QSTR_max_duration), MP_ROM_PTR(&esp32_rmt_max_pulse_length_obj) }, + { MP_ROM_QSTR(MP_QSTR_source_freq), MP_ROM_PTR(&esp32_rmt_source_freq_obj) }, + { MP_ROM_QSTR(MP_QSTR_clock_div), MP_ROM_PTR(&esp32_rmt_clock_div_obj) }, { MP_ROM_QSTR(MP_QSTR_wait_done), MP_ROM_PTR(&esp32_rmt_wait_done_obj) }, { MP_ROM_QSTR(MP_QSTR_loop), MP_ROM_PTR(&esp32_rmt_loop_obj) }, { MP_ROM_QSTR(MP_QSTR_send_pulses), MP_ROM_PTR(&esp32_rmt_send_pulses_obj) }, From 9a6038daa80097ae57fa020229c3a2290d3570b1 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Mon, 16 Dec 2019 14:04:27 +1100 Subject: [PATCH 16/20] Update RMT documentation --- docs/esp32/general.rst | 2 +- docs/esp32/quickref.rst | 33 ++++++++++++++++--------------- docs/library/esp32.rst | 44 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/docs/esp32/general.rst b/docs/esp32/general.rst index 3894eccc555bf..8137c042d9214 100644 --- a/docs/esp32/general.rst +++ b/docs/esp32/general.rst @@ -52,7 +52,7 @@ For your convenience, some of technical specifications are provided below: * I2S: 2 * ADC: 12-bit SAR ADC up to 18 channels * DAC: 2 8-bit DACs -* RMT +* RMT: 8 channels allowing accurate pulse transmit/receive * Programming: using BootROM bootloader from UART - due to external FlashROM and always-available BootROM bootloader, the ESP32 is not brickable diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index b1500abbafdbb..ac209ddf3edad 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -373,31 +373,32 @@ to send and receive infrared remote control signals. However, due to a flexible design and very accurate (as low as 12.5ns) pulse generation, it can also be used to transmit or receive many other types of digital signals:: - r = esp32.RMT(0, Pin(18), 80) - r # RMT(channel=0, pin=18, clock_divider=80) - r.resolution(), r.max_pulse_length() # (1e-06, 0.032768) - r.send_pulses((100, 2000, 100, 4000), start=0) # Send 0 for 100*1e-06s, 1 for 2000*1e-06s etc - -The input to the RMT module is an 80MHz clock. ``clock_divider`` *divides* the -clock input which determines the minimum *resolution* of a pulse. The numbers -specificed in ``send_pulses`` are *multiplied* by the minimum resolution to + r = esp32.RMT(0, pin=Pin(18), clock_div=8) + r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8) + # The *resolution* is 100ns (1/(source_freq/clock_div)). + r.send_pulses((1, 20, 2, 40), start=0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns + +The input to the RMT module is an 80MHz clock (in the future it may be able to +configure the input clock but, for now, it's fixed). ``clock_div`` *divides* +the clock input which determines the *resolution* of the RMT channel. The +numbers specificed in ``send_pulses`` are *multiplied* by the resolution to define the pulses. -``clock_divider`` is an 8-bit divider (0-255) and each pulse can be defined by -multiplying the minimum resolution by a 15-bit (0-32,768) number. There are -eight channels (0-7) and each can have a different clock divider. +``clock_div`` is an 8-bit divider (0-255) and each pulse can be defined by +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. -So, in the example above, the 80MHz clock is divided by 80. Thus the minimum -resolution is (1/(80Mhz/80)) 1µs. Since the ``start`` level is 0 and toggles -with each number, the bitstream is ``0101`` with durations of [100µs, 2ms, -100µs, 4ms]. +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, +100ns, 4000ns]. For more details see Espressif's `ESP-IDF RMT documentation. `_. .. Warning:: The current MicroPython RMT implementation lacks some features, most notably receiving pulses - and carrier transmit. + and carrier transmit. It's also a *beta feature* and the interface may change in the future. OneWire driver -------------- diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index a593965ae2740..3c6581658a2bc 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -101,6 +101,50 @@ The Ultra-Low-Power co-processor Start the ULP running at the given *entry_point*. +RMT +--- + +.. class:: RMT(channel, pin, clock_div) + + 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. + +.. method:: RMT.source_freq() + + Returns the source clock frequency. Currently the source clock is not + configurable so this will always return 80MHz. + +.. method:: RMT.clock_div() + + Return the clock divider. Note that *resolution* is + ``1/(source_freq/clock_div)``. + +.. method:: RMT.wait_done(timeout=0) + + Returns True if ``send_pulses`` has completed. + + If *timeout* (defined in ticks of ``source_freq/clock_div``) is specified + the method will wait for *timeout* or until ``send_pulses`` is complete, + returning False if the channel continues to transmit. + +.. Warning:: + Avoid using ``wait_done()`` if looping is enabled. + +.. method:: RMT.loop(enable_loop) + + Configure looping on the channel, allowing a stream of pulses to be + indefinitely repeated. *enable_loop* is bool, set to True to enable looping. + +.. method:: RMT.send_pulses(pulses, start) + + 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. + Constants --------- From 4430e61fcc2f3c1b949eca64cdb87ae6d9879c95 Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Mon, 16 Dec 2019 14:06:24 +1100 Subject: [PATCH 17/20] Rename clock_divider->clock_div, make it an optional keyword parameter. Change pin to a required keyword parameter. --- ports/esp32/esp32_rmt.c | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 7eb40c6e6b32f..87e0a639d55b7 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -56,7 +56,7 @@ typedef struct _esp32_rmt_obj_t { mp_obj_base_t base; uint8_t channel_id; gpio_num_t pin; - uint8_t clock_divider; + uint8_t clock_div; mp_uint_t num_items; rmt_item32_t* items; } esp32_rmt_obj_t; @@ -75,13 +75,19 @@ STATIC esp_err_t check_esp_err(esp_err_t code) { } -STATIC mp_obj_t esp32_rmt_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, 3, MP_OBJ_FUN_ARGS_MAX, true); - mp_uint_t channel_id = mp_obj_get_int(args[0]); - gpio_num_t pin_id = machine_pin_get_id(args[1]); - mp_uint_t clock_divider = mp_obj_get_int(args[2]); +STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + static const mp_arg_t allowed_args[] = { + { 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_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; - if (clock_divider < 1 || clock_divider > 255) { + if (clock_div < 1 || clock_div > 255) { mp_raise_ValueError("Clock divider must be between 1 and 255"); } @@ -89,7 +95,7 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz self->base.type = &esp32_rmt_type; self->channel_id = channel_id; self->pin = pin_id; - self->clock_divider = clock_divider; + self->clock_div = clock_div; rmt_config_t config; config.rmt_mode = RMT_MODE_TX; @@ -105,7 +111,7 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz config.tx_config.carrier_freq_hz = 0; config.tx_config.carrier_level = 1; - config.clk_div = self->clock_divider; + config.clk_div = self->clock_div; check_esp_err(rmt_config(&config)); check_esp_err(rmt_driver_install(config.channel, 0, 0)); @@ -118,7 +124,7 @@ STATIC void esp32_rmt_print(const mp_print_t *print, mp_obj_t self_in, mp_print_ 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)", - self->channel_id, self->pin, APB_CLK_FREQ, self->clock_divider); + self->channel_id, self->pin, APB_CLK_FREQ, self->clock_div); } else { mp_printf(print, "RMT()"); } @@ -152,7 +158,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_source_freq_obj, esp32_rmt_source_fre // Return the clock divider. STATIC mp_obj_t esp32_rmt_clock_div(mp_obj_t self_in) { esp32_rmt_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_int(self->clock_divider); + return mp_obj_new_int(self->clock_div); } STATIC MP_DEFINE_CONST_FUN_OBJ_1(esp32_rmt_clock_div_obj, esp32_rmt_clock_div); From 6f39223114feb2d9d56b574626f53deac1d9a53e Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Mon, 16 Dec 2019 21:48:41 +1100 Subject: [PATCH 18/20] Provide more explanation of RMT in the reference documentation, moving it from the Quick Ref --- docs/esp32/quickref.rst | 36 +++++++++------------------------ docs/library/esp32.rst | 45 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 52 insertions(+), 29 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index ac209ddf3edad..417549a58de4e 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -368,37 +368,19 @@ Notes: RMT --- -The RMT (Remote Control) module, specific to the ESP32, was originally designed -to send and receive infrared remote control signals. However, due to a flexible -design and very accurate (as low as 12.5ns) pulse generation, it can also be -used to transmit or receive many other types of digital signals:: +See :ref:`esp32.RMT ` - r = esp32.RMT(0, pin=Pin(18), clock_div=8) - r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8) - # The *resolution* is 100ns (1/(source_freq/clock_div)). - r.send_pulses((1, 20, 2, 40), start=0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns - -The input to the RMT module is an 80MHz clock (in the future it may be able to -configure the input clock but, for now, it's fixed). ``clock_div`` *divides* -the clock input which determines the *resolution* of the RMT channel. The -numbers specificed in ``send_pulses`` are *multiplied* by the resolution to -define the pulses. +The RMT is ESP32-specific and allows generation of accurate digital pulses with +12.5ns resolution. Usage is:: -``clock_div`` is an 8-bit divider (0-255) and each pulse can be defined by -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. - -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, -100ns, 4000ns]. + import esp32 + from machine import Pin -For more details see Espressif's `ESP-IDF RMT documentation. -`_. + r = esp32.RMT(0, pin=Pin(18), clock_div=8) + r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8) + # The channel resolution is 100ns (1/(source_freq/clock_div)). + r.send_pulses((1, 20, 2, 40), start=0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns -.. Warning:: - The current MicroPython RMT implementation lacks some features, most notably receiving pulses - and carrier transmit. It's also a *beta feature* and the interface may change in the future. OneWire driver -------------- diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index 3c6581658a2bc..430c69b45f206 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -1,3 +1,5 @@ +.. currentmodule:: esp32 + :mod:`esp32` --- functionality specific to the ESP32 ==================================================== @@ -101,10 +103,49 @@ The Ultra-Low-Power co-processor Start the ULP running at the given *entry_point*. +.. _esp32.RMT: + RMT --- -.. class:: RMT(channel, pin, clock_div) +The RMT (Remote Control) module, specific to the ESP32, was originally designed +to send and receive infrared remote control signals. However, due to a flexible +design and very accurate (as low as 12.5ns) pulse generation, it can also be +used to transmit or receive many other types of digital signals:: + + import esp32 + from machine import Pin + + r = esp32.RMT(0, pin=Pin(18), clock_div=8) + r # RMT(channel=0, pin=18, source_freq=80000000, clock_div=8) + # The channel resolution is 100ns (1/(source_freq/clock_div)). + r.send_pulses((1, 20, 2, 40), start=0) # Send 0 for 100ns, 1 for 2000ns, 0 for 200ns, 1 for 4000ns + +The input to the RMT module is an 80MHz clock (in the future it may be able to +configure the input clock but, for now, it's fixed). ``clock_div`` *divides* +the clock input which determines the resolution of the RMT channel. The +numbers specificed in ``send_pulses`` are multiplied by the resolution to +define the pulses. + +``clock_div`` is an 8-bit divider (0-255) and each pulse can be defined by +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. + +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, +100ns, 4000ns]. + +For more details see Espressif's `ESP-IDF RMT documentation. +`_. + +.. Warning:: + The current MicroPython RMT implementation lacks some features, most notably + receiving pulses and carrier transmit. RMT can also be considered a *beta + feature* and the interface may change in the future. + + +.. class:: RMT(channel, pin, clock_div=8) 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*, @@ -119,7 +160,7 @@ RMT .. method:: RMT.clock_div() - Return the clock divider. Note that *resolution* is + Return the clock divider. Note that the channel resolution is ``1/(source_freq/clock_div)``. .. method:: RMT.wait_done(timeout=0) From 801507e6ec1968e59c50dc4e6f02777f7f4dad4d Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Mon, 16 Dec 2019 21:50:50 +1100 Subject: [PATCH 19/20] Remove debug printf's --- ports/esp32/esp32_rmt.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 87e0a639d55b7..ee1c8184c7b43 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -226,23 +226,19 @@ STATIC mp_obj_t esp32_rmt_send_pulses(size_t n_args, const mp_obj_t *pos_args, m mp_uint_t num_items = (pulses_length / 2) + (pulses_length % 2); if (num_items > self->num_items) { - //printf("realloc required, old=%u, new=%u\n", self->num_items, num_items); self->items = (rmt_item32_t*)m_realloc(self->items, num_items * sizeof(rmt_item32_t *)); self->num_items = num_items; } - //printf("num_items=%d pulses_length=%d\n\n", num_items, pulses_length); 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. - //printf("duration=%d start=%d\n", items[item_index].duration0, items[item_index].level0); if (pulse_index < pulses_length) { self->items[item_index].duration1 = mp_obj_get_int(pulses_ptr[pulse_index]); self->items[item_index].level1 = start++; - //printf("duration=%d start=%d\n", items[item_index].duration1, items[item_index].level1); } } check_esp_err(rmt_write_items(self->channel_id, self->items, num_items, false /* non-blocking */)); From ab45debabf15a8084f8bf53d719488b1abb5e43d Mon Sep 17 00:00:00 2001 From: Matt Trentini Date: Wed, 18 Dec 2019 22:12:39 +1100 Subject: [PATCH 20/20] Prefer to use the non-legacy type checks --- ports/esp32/esp32_rmt.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index ee1c8184c7b43..031e5a5dabec9 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -215,9 +215,9 @@ STATIC mp_obj_t esp32_rmt_send_pulses(size_t n_args, const mp_obj_t *pos_args, m mp_uint_t pulses_length = 0; mp_obj_t* pulses_ptr = NULL; - if(MP_OBJ_IS_TYPE(pulses, &mp_type_tuple) == true) { + if(mp_obj_is_type(pulses, &mp_type_tuple) == true) { mp_obj_tuple_get(pulses, &pulses_length, &pulses_ptr); - } else if(MP_OBJ_IS_TYPE(pulses, &mp_type_list) == true) { + } else if(mp_obj_is_type(pulses, &mp_type_list) == true) { mp_obj_list_get(pulses, &pulses_length, &pulses_ptr); } else { mp_raise_TypeError("Pulses must be specified with a tuple or list");