diff --git a/docs/library/esp32.rst b/docs/library/esp32.rst index dfde840a2141a..f6b9006ba92ed 100644 --- a/docs/library/esp32.rst +++ b/docs/library/esp32.rst @@ -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 @@ -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, @@ -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() @@ -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 diff --git a/ports/esp32/esp32_rmt.c b/ports/esp32/esp32_rmt.c index 7971ca5d1c5af..70121ce1b9d0b 100644 --- a/ports/esp32/esp32_rmt.c +++ b/ports/esp32/esp32_rmt.c @@ -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; @@ -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; @@ -64,27 +65,46 @@ 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); @@ -92,23 +112,19 @@ STATIC mp_obj_t esp32_rmt_make_new(const mp_obj_type_t *type, size_t n_args, siz 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; @@ -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()"); } @@ -197,27 +207,78 @@ 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; @@ -225,11 +286,15 @@ STATIC mp_obj_t esp32_rmt_write_pulses(size_t n_args, const mp_obj_t *pos_args, 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; } }