diff --git a/cores/esp32/esp32-hal-rgb-led.c b/cores/esp32/esp32-hal-rgb-led.c index 61558b86ee6..c74c812f3bb 100644 --- a/cores/esp32/esp32-hal-rgb-led.c +++ b/cores/esp32/esp32-hal-rgb-led.c @@ -3,23 +3,20 @@ void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val){ rmt_data_t led_data[24]; - static rmt_obj_t* rmt_send = NULL; static bool initialized = false; uint8_t _pin = pin; #ifdef RGB_BUILTIN if(pin == RGB_BUILTIN){ - _pin = RGB_BUILTIN-SOC_GPIO_PIN_COUNT; + _pin = RGB_BUILTIN - SOC_GPIO_PIN_COUNT; } #endif if(!initialized){ - if((rmt_send = rmtInit(_pin, RMT_TX_MODE, RMT_MEM_64)) == NULL){ + if (!rmtInit(_pin, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)){ log_e("RGB LED driver initialization failed!"); - rmt_send = NULL; return; } - rmtSetTick(rmt_send, 100); initialized = true; } @@ -43,5 +40,5 @@ void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue i++; } } - rmtWrite(rmt_send, led_data, 24); + rmtWrite(_pin, led_data, RMT_SYMBOLS_OF(led_data), RMT_WAIT_FOR_EVER); } diff --git a/cores/esp32/esp32-hal-rmt.c b/cores/esp32/esp32-hal-rmt.c index 309cd13b327..75ce1f41ac1 100644 --- a/cores/esp32/esp32-hal-rmt.c +++ b/cores/esp32/esp32-hal-rmt.c @@ -1,4 +1,4 @@ -// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -13,622 +13,556 @@ // limitations under the License. #include "esp32-hal.h" -#include "driver/rmt.h" +#include "driver/gpio.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_rx.h" -/** - * Internal macros - */ +#include "esp32-hal-rmt.h" +#include "esp32-hal-periman.h" -#define MAX_CHANNELS (SOC_RMT_GROUPS * SOC_RMT_CHANNELS_PER_GROUP) +// Arduino Task Handle indicates if the Arduino Task has been started already +extern TaskHandle_t loopTaskHandle; -#define RMT_TX_CH_START (0) -#define RMT_TX_CH_END (SOC_RMT_TX_CANDIDATES_PER_GROUP - 1) -#define RMT_RX_CH_START (SOC_RMT_CHANNELS_PER_GROUP - SOC_RMT_TX_CANDIDATES_PER_GROUP) -#define RMT_RX_CH_END (SOC_RMT_CHANNELS_PER_GROUP - 1) +// RMT Events +#define RMT_FLAG_RX_DONE (1) +#define RMT_FLAG_TX_DONE (2) -#define _LIMIT(a,b) (a>b?b:a) +/** + Internal macros +*/ #if CONFIG_DISABLE_HAL_LOCKS -# define RMT_MUTEX_LOCK(channel) -# define RMT_MUTEX_UNLOCK(channel) +# define RMT_MUTEX_LOCK(busptr) +# define RMT_MUTEX_UNLOCK(busptr) #else -# define RMT_MUTEX_LOCK(channel) do {} while (xSemaphoreTake(g_rmt_objlocks[channel], portMAX_DELAY) != pdPASS) -# define RMT_MUTEX_UNLOCK(channel) xSemaphoreGive(g_rmt_objlocks[channel]) +# define RMT_MUTEX_LOCK(busptr) do {} while (xSemaphoreTake(busptr->g_rmt_objlocks, portMAX_DELAY) != pdPASS) +# define RMT_MUTEX_UNLOCK(busptr) xSemaphoreGive(busptr->g_rmt_objlocks) #endif /* CONFIG_DISABLE_HAL_LOCKS */ -//#define _RMT_INTERNAL_DEBUG -#ifdef _RMT_INTERNAL_DEBUG -# define DEBUG_INTERRUPT_START(pin) digitalWrite(pin, 1); -# define DEBUG_INTERRUPT_END(pin) digitalWrite(pin, 0); -#else -# define DEBUG_INTERRUPT_START(pin) -# define DEBUG_INTERRUPT_END(pin) -#endif /* _RMT_INTERNAL_DEBUG */ - -#define RMT_DEFAULT_ARD_CONFIG_TX(gpio, channel_id, buffers) \ - { \ - .rmt_mode = RMT_MODE_TX, \ - .channel = channel_id, \ - .gpio_num = gpio, \ - .clk_div = 1, \ - .mem_block_num = buffers, \ - .flags = 0, \ - .tx_config = { \ - .carrier_level = RMT_CARRIER_LEVEL_HIGH, \ - .idle_level = RMT_IDLE_LEVEL_LOW, \ - .carrier_duty_percent = 50, \ - .carrier_en = false, \ - .loop_en = false, \ - .idle_output_en = true, \ - } \ - } -#define RMT_DEFAULT_ARD_CONFIG_RX(gpio, channel_id, buffers) \ - { \ - .rmt_mode = RMT_MODE_RX, \ - .channel = channel_id, \ - .gpio_num = gpio, \ - .clk_div = 1, \ - .mem_block_num = buffers, \ - .flags = 0, \ - .rx_config = { \ - .idle_threshold = 0x80, \ - .filter_ticks_thresh = 100, \ - .filter_en = false, \ - } \ - } +/** + Typedefs for internal stuctures, enums +*/ +struct rmt_obj_s { + // general RMT information + rmt_channel_handle_t rmt_channel_h; // IDF RMT channel handler + rmt_encoder_handle_t rmt_copy_encoder_h; // RMT simple copy encoder handle + uint32_t signal_range_min_ns; // RX Filter data - Low Pass pulse width + uint32_t signal_range_max_ns; // RX idle time that defines end of reading + EventGroupHandle_t rmt_events; // read/write done event RMT callback handle + bool rmt_ch_is_looping; // Is this RMT TX Channel in LOOPING MODE? + size_t *num_symbols_read; // Pointer to the number of RMT symbol read by IDF RMT RX Done -/** - * Typedefs for internal stuctures, enums - */ -struct rmt_obj_s -{ - bool allocated; - EventGroupHandle_t events; - uint32_t channel; - uint32_t buffers; - uint32_t data_size; - uint32_t* data_ptr; - rmt_rx_data_cb_t cb; - void * arg; - TaskHandle_t rxTaskHandle; - bool rx_completed; - bool tx_not_rx; -}; - -/** - * Internal variables for channel descriptors - */ -static xSemaphoreHandle g_rmt_objlocks[MAX_CHANNELS] = { - NULL, NULL, NULL, NULL, -#if MAX_CHANNELS > 4 - NULL, NULL, NULL, NULL -#endif +#if !CONFIG_DISABLE_HAL_LOCKS + xSemaphoreHandle g_rmt_objlocks; // Channel Semaphore Lock +#endif /* CONFIG_DISABLE_HAL_LOCKS */ }; -static rmt_obj_t g_rmt_objects[MAX_CHANNELS] = { - { false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, true, true}, - { false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, true, true}, - { false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, true, true}, - { false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, true, true}, -#if MAX_CHANNELS > 4 - { false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, true, true}, - { false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, true, true}, - { false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, true, true}, - { false, NULL, 0, 0, 0, NULL, NULL, NULL, NULL, true, true}, -#endif -}; +typedef struct rmt_obj_s *rmt_bus_handle_t; /** - * Internal variables for driver data - */ + Internal variables used in RMT API +*/ static xSemaphoreHandle g_rmt_block_lock = NULL; /** - * Internal method (private) declarations - */ + Internal method (private) declarations +*/ -static rmt_obj_t* _rmtAllocate(int pin, int from, int size) +// This is called from an IDF ISR code, therefore this code is part of an ISR +static bool _rmt_rx_done_callback(rmt_channel_handle_t channel, const rmt_rx_done_event_data_t *data, void *args) { - int i; - // setup how many buffers shall we use - g_rmt_objects[from].buffers = size; - - for (i=0; inum_symbols_read = data->num_symbols; + // set RX event group and signal the received RMT symbols of that channel + xEventGroupSetBitsFromISR(bus->rmt_events, RMT_FLAG_RX_DONE, &high_task_wakeup); + // A "need to yield" is returned in order to execute portYIELD_FROM_ISR() in the main IDF RX ISR + return high_task_wakeup == pdTRUE; } -void _rmtDumpStatus(rmt_obj_t* rmt) +// This is called from an IDF ISR code, therefore this code is part of an ISR +static bool _rmt_tx_done_callback(rmt_channel_handle_t channel, const rmt_tx_done_event_data_t *data, void *args) { - bool loop_en; - uint8_t div_cnt; - uint8_t memNum; - bool lowPowerMode; - rmt_mem_owner_t owner; - uint16_t idleThreshold; - uint32_t status; - rmt_source_clk_t srcClk; - rmt_channel_t channel = rmt->channel; - -RMT_MUTEX_LOCK(channel); - rmt_get_tx_loop_mode(channel, &loop_en); - rmt_get_clk_div(channel, &div_cnt); - rmt_get_mem_block_num(channel, &memNum); - rmt_get_mem_pd(channel, &lowPowerMode); - rmt_get_memory_owner(channel, &owner); - rmt_get_rx_idle_thresh(channel, &idleThreshold); - rmt_get_status(channel, &status); - rmt_get_source_clk(channel, &srcClk); - - log_d("Status for RMT channel %d", channel); - log_d("- Loop enabled: %d", loop_en); - log_d("- Clock divisor: %d", div_cnt); - log_d("- Number of memory blocks: %d", memNum); - log_d("- Low power mode: %d", lowPowerMode); - log_d("- Memory owner: %s", owner==RMT_MEM_OWNER_TX?"TX":"RX"); - log_d("- Idle threshold: %d", idleThreshold); - log_d("- Status: %d", status); - log_d("- Source clock: %s", srcClk==RMT_BASECLK_APB?"APB (80MHz)":"1MHz"); -RMT_MUTEX_UNLOCK(channel); + BaseType_t high_task_wakeup = pdFALSE; + rmt_bus_handle_t bus = (rmt_bus_handle_t) args; + // set RX event group and signal the received RMT symbols of that channel + xEventGroupSetBitsFromISR(bus->rmt_events, RMT_FLAG_TX_DONE, &high_task_wakeup); + // A "need to yield" is returned in order to execute portYIELD_FROM_ISR() in the main IDF RX ISR + return high_task_wakeup == pdTRUE; } -static void _rmtRxTask(void *args) { - rmt_obj_t *rmt = (rmt_obj_t *) args; - RingbufHandle_t rb = NULL; - size_t rmt_len = 0; - rmt_item32_t *data = NULL; - - if (!rmt) { - log_e(" -- Inavalid Argument"); - goto err; - } - - int channel = rmt->channel; - rmt_get_ringbuf_handle(channel, &rb); - if (!rb) { - log_e(" -- Failed to get RMT ringbuffer handle"); - goto err; - } - - for(;;) { - data = (rmt_item32_t *) xRingbufferReceive(rb, &rmt_len, portMAX_DELAY); - if (data) { - log_d(" -- Got %d bytes on RX Ringbuffer - CH %d", rmt_len, rmt->channel); - rmt->rx_completed = true; // used in rmtReceiveCompleted() - // callback - if (rmt->cb) { - (rmt->cb)((uint32_t *)data, rmt_len / sizeof(rmt_item32_t), rmt->arg); - } else { - // stop RX -- will force a correct call with a callback pointer / new rmtReadData() / rmtReadAsync() - rmt_rx_stop(channel); - } - // Async Read -- copy data to caller - if (rmt->data_ptr && rmt->data_size) { - uint32_t data_size = rmt->data_size; - uint32_t read_len = rmt_len / sizeof(rmt_item32_t); - if (read_len < rmt->data_size) data_size = read_len; - rmt_item32_t *p = (rmt_item32_t *)rmt->data_ptr; - for (uint32_t i = 0; i < data_size; i++) { - p[i] = data[i]; - } - } - // set events - if (rmt->events) { - xEventGroupSetBits(rmt->events, RMT_FLAG_RX_DONE); - } - vRingbufferReturnItem(rb, (void *) data); - } // xRingbufferReceive - } // for(;;) - -err: - vTaskDelete(NULL); -} - - -static bool _rmtCreateRxTask(rmt_obj_t* rmt) +// This function must be called only after checking the pin and its bus with _rmtGetBus() +static bool _rmtCheckDirection(uint8_t gpio_num, rmt_ch_dir_t rmt_dir, const char* labelFunc) { - if (!rmt) { - return false; - } - if (rmt->rxTaskHandle) { // Task already created - return false; - } - - xTaskCreate(_rmtRxTask, "rmt_rx_task", 4096, rmt, 20, &rmt->rxTaskHandle); + // gets bus RMT direction from the Peripheral Manager information + rmt_ch_dir_t bus_rmt_dir = perimanGetPinBusType(gpio_num) == ESP32_BUS_TYPE_RMT_TX ? RMT_TX_MODE : RMT_RX_MODE; - if(rmt->rxTaskHandle == NULL){ - log_e("RMT RX Task create failed"); - return false; - } + if (bus_rmt_dir == rmt_dir) { // matches expected RX/TX channel return true; + } + + // print error message + if (rmt_dir == RMT_RX_MODE) { + log_w("==>%s():Channel set as TX instead of RX.", labelFunc); + } else { + log_w("==>%s():Channel set as RX instead of TX.", labelFunc); + } + return false; // mismatched } -// Helper function to test if an RMT channel is correctly assigned to TX or RX, issuing an error message if necessary -// Also test RMT pointer for NULL and returns false in case it is NULL -// return true when it is correctly assigned, false otherwise -static bool _rmtCheckTXnotRX(rmt_obj_t* rmt, bool tx_not_rx) +static rmt_bus_handle_t _rmtGetBus(int pin, const char* labelFunc) { - if (!rmt) { // also returns false on NULL - return false; - } - - if (rmt->tx_not_rx == tx_not_rx) { // matches expected RX/TX channel - return true; - } - - if (tx_not_rx) { // expected TX channel - log_e("Can't write on a RX RMT Channel"); - } else{ // expected RX channel - log_e("Can't read on a TX RMT Channel"); - } - return false; // missmatched + // Is pin RX or TX? Let's find it out + peripheral_bus_type_t rmt_bus_type = perimanGetPinBusType(pin); + if (rmt_bus_type != ESP32_BUS_TYPE_RMT_TX && rmt_bus_type != ESP32_BUS_TYPE_RMT_RX) { + log_e("==>%s():GPIO %u is not attached to an RMT channel.", labelFunc, pin); + return NULL; + } + + return (rmt_bus_handle_t)perimanGetPinBus(pin, rmt_bus_type); } -/** - * Public method definitions - */ - -bool rmtSetCarrier(rmt_obj_t* rmt, bool carrier_en, bool carrier_level, uint32_t low, uint32_t high) +// Peripheral Manager detach callback +static bool _rmtDetachBus(void *busptr) { - if (!_rmtCheckTXnotRX(rmt, RMT_TX_MODE) || low > 0xFFFF || high > 0xFFFF) { - return false; - } - size_t channel = rmt->channel; + // sanity check - it should never happen + assert(busptr && "_rmtDetachBus bus NULL pointer."); + + bool retCode = true; + rmt_bus_handle_t bus = (rmt_bus_handle_t) busptr; + log_v("Detaching RMT GPIO Bus"); + + // lock it + while (xSemaphoreTake(g_rmt_block_lock, portMAX_DELAY) != pdPASS) {} + + // free Event Group + if (bus->rmt_events != NULL) { + vEventGroupDelete(bus->rmt_events); + bus->rmt_events = NULL; + } + // deallocate the channel encoder + if (bus->rmt_copy_encoder_h != NULL) { + if (ESP_OK != rmt_del_encoder(bus->rmt_copy_encoder_h)) { + log_w("RMT Encoder Deletion has failed."); + retCode = false; + } + } + // disable and deallocate RMT channel + if (bus->rmt_channel_h != NULL) { + // force stopping rmt TX/RX processing and unlock Power Management (APB Freq) + rmt_disable(bus->rmt_channel_h); + if (ESP_OK != rmt_del_channel(bus->rmt_channel_h)) { + log_w("RMT Channel Deletion has failed."); + retCode = false; + } + } +#if !CONFIG_DISABLE_HAL_LOCKS + // deallocate channel semaphore + if (bus->g_rmt_objlocks != NULL) { + vSemaphoreDelete(bus->g_rmt_objlocks); + } +#endif + // free the allocated bus data structure + free(bus); - RMT_MUTEX_LOCK(channel); - rmt_set_tx_carrier(channel, carrier_en, high, low, carrier_level); - RMT_MUTEX_UNLOCK(channel); - return true; + // release the mutex + xSemaphoreGive(g_rmt_block_lock); + return retCode; } -bool rmtSetFilter(rmt_obj_t* rmt, bool filter_en, uint32_t filter_level) -{ - if (!_rmtCheckTXnotRX(rmt, RMT_RX_MODE) || filter_level > 0xFF) { - return false; - } - size_t channel = rmt->channel; - - RMT_MUTEX_LOCK(channel); - rmt_set_rx_filter(channel, filter_en, filter_level); - RMT_MUTEX_UNLOCK(channel); - return true; -} +/** + Public method definitions +*/ -bool rmtSetRxThreshold(rmt_obj_t* rmt, uint32_t value) +bool rmtSetCarrier(int pin, bool carrier_en, bool carrier_level, uint32_t frequency_Hz, float duty_percent) { - if (!_rmtCheckTXnotRX(rmt, RMT_RX_MODE) || value > 0xFFFF) { - return false; - } - size_t channel = rmt->channel; - - RMT_MUTEX_LOCK(channel); - rmt_set_rx_idle_thresh(channel, value); - RMT_MUTEX_UNLOCK(channel); - return true; + rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__); + if (bus == NULL) { + return false; + } + + if (duty_percent > 1) { + log_w("GPIO %d - RMT Carrier must be a float percentage from 0 to 1. Setting to 50%.", pin); + duty_percent = 0.5; + } + rmt_carrier_config_t carrier_cfg = {0}; + carrier_cfg.duty_cycle = duty_percent; // duty cycle + carrier_cfg.frequency_hz = carrier_en ? frequency_Hz : 0; // carrier frequency in Hz + carrier_cfg.flags.polarity_active_low = carrier_level; // carrier modulation polarity level + + bool retCode = true; + RMT_MUTEX_LOCK(bus); + // modulate carrier to TX channel + if (ESP_OK != rmt_apply_carrier(bus->rmt_channel_h, &carrier_cfg)) { + log_w("GPIO %d - Error applying RMT carrier.", pin); + retCode = false; + } + RMT_MUTEX_UNLOCK(bus); + + return retCode; } - -bool rmtDeinit(rmt_obj_t *rmt) +bool rmtSetFilter(int pin, uint8_t filter_pulse_ns) { - if (!rmt) { - return false; - } - - // sanity check - if (rmt != &(g_rmt_objects[rmt->channel])) { - return false; - } - - RMT_MUTEX_LOCK(rmt->channel); - // force stopping rmt processing - if (rmt->tx_not_rx) { - rmt_tx_stop(rmt->channel); - } else { - rmt_rx_stop(rmt->channel); - if(rmt->rxTaskHandle){ - vTaskDelete(rmt->rxTaskHandle); - rmt->rxTaskHandle = NULL; - } - } - - rmt_driver_uninstall(rmt->channel); - - size_t from = rmt->channel; - size_t to = rmt->buffers + rmt->channel; - size_t i; - - for (i = from; i < to; i++) { - g_rmt_objects[i].allocated = false; - } - - g_rmt_objects[from].channel = 0; - g_rmt_objects[from].buffers = 0; - RMT_MUTEX_UNLOCK(rmt->channel); - -#if !CONFIG_DISABLE_HAL_LOCKS - if(g_rmt_objlocks[from] != NULL) { - vSemaphoreDelete(g_rmt_objlocks[from]); - g_rmt_objlocks[from] = NULL; - } -#endif - - return true; + rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__); + if (bus == NULL) { + return false; + } + + if (!_rmtCheckDirection(pin, RMT_RX_MODE, __FUNCTION__)) { + return false; + } + + RMT_MUTEX_LOCK(bus); + bus->signal_range_min_ns = filter_pulse_ns; // set zero to disable it + RMT_MUTEX_UNLOCK(bus); + return true; } -bool rmtLoop(rmt_obj_t* rmt, rmt_data_t* data, size_t size) +bool rmtSetRxThreshold(int pin, uint16_t value) { - if (!_rmtCheckTXnotRX(rmt, RMT_TX_MODE)) { - return false; - } - int channel = rmt->channel; - RMT_MUTEX_LOCK(channel); - rmt_tx_stop(channel); - rmt_set_tx_loop_mode(channel, true); - rmt_write_items(channel, (const rmt_item32_t *)data, size, false); - RMT_MUTEX_UNLOCK(channel); - return true; + rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__); + if (bus == NULL) { + return false; + } + + if (!_rmtCheckDirection(pin, RMT_RX_MODE, __FUNCTION__)) { + return false; + } + + RMT_MUTEX_LOCK(bus); + bus->signal_range_max_ns = value; // set as zero to disable it + RMT_MUTEX_UNLOCK(bus); + return true; } -bool rmtWrite(rmt_obj_t* rmt, rmt_data_t* data, size_t size) +bool rmtDeinit(int pin) { - if (!_rmtCheckTXnotRX(rmt, RMT_TX_MODE)) { - return false; - } - int channel = rmt->channel; - RMT_MUTEX_LOCK(channel); - rmt_tx_stop(channel); - rmt_set_tx_loop_mode(channel, false); - rmt_write_items(channel, (const rmt_item32_t *)data, size, false); - RMT_MUTEX_UNLOCK(channel); - return true; + log_v("Deiniting RMT GPIO %d", pin); + if (_rmtGetBus(pin, __FUNCTION__) != NULL) { + // release all allocated data + return perimanSetPinBus(pin, ESP32_BUS_TYPE_INIT, NULL); + } + log_e("GPIO %d - No RMT channel associated.", pin); + return false; } -bool rmtWriteBlocking(rmt_obj_t* rmt, rmt_data_t* data, size_t size) +static bool _rmtWrite(int pin, rmt_data_t* data, size_t num_rmt_symbols, bool blocking, bool loop, uint32_t timeout_ms) { - if (!_rmtCheckTXnotRX(rmt, RMT_TX_MODE)) { - return false; + rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__); + if (bus == NULL) { + return false; + } + if (!_rmtCheckDirection(pin, RMT_TX_MODE, __FUNCTION__)) { + return false; + } + bool loopCancel = false; // user wants to cancel the writing loop mode + if (data == NULL || num_rmt_symbols == 0) { + if (!loop) { + log_w("GPIO %d - RMT Write Data NULL pointer or size is zero.", pin); + return false; + } else { + loopCancel = true; + } + } + + log_v("GPIO: %d - Request: %d RMT Symbols - %s - Timeout: %d", pin, num_rmt_symbols, blocking ? "Blocking" : "Non-Blocking", timeout_ms); + log_v("GPIO: %d - Currently in Loop Mode: [%s] | Asked to Loop: %s, LoopCancel: %s", pin, bus->rmt_ch_is_looping ? "YES" : "NO", loop ? "YES" : "NO", loopCancel ? "YES" : "NO"); + + if ((xEventGroupGetBits(bus->rmt_events) & RMT_FLAG_TX_DONE) == 0) { + log_v("GPIO %d - RMT Write still pending to be completed.", pin); + return false; + } + + rmt_transmit_config_t transmit_cfg = {0}; // loop mode disabled + bool retCode = true; + + RMT_MUTEX_LOCK(bus); + // wants to start in writing or looping over a previous looping --> resets the channel + if (bus->rmt_ch_is_looping == true) { + // must force stopping a previous loop transmission first + rmt_disable(bus->rmt_channel_h); + // enable it again for looping or writing + rmt_enable(bus->rmt_channel_h); + bus->rmt_ch_is_looping = false; // not looping anymore + } + if (loopCancel) { + // just resets and releases the channel, maybe, already done above, then exits + bus->rmt_ch_is_looping = false; + } else { // new writing | looping request + // looping | Writing over a previous looping state is valid + if (loop) { + transmit_cfg.loop_count = -1; // enable infinite loop mode + // keeps RMT_FLAG_TX_DONE set - it never changes + } else { + // looping mode never sets this flag (IDF 5.1) in the callback + xEventGroupClearBits(bus->rmt_events, RMT_FLAG_TX_DONE); + } + // transmits just once or looping data + if (ESP_OK != rmt_transmit(bus->rmt_channel_h, bus->rmt_copy_encoder_h, + (const void *) data, num_rmt_symbols * sizeof(rmt_data_t), &transmit_cfg)) { + retCode = false; + log_w("GPIO %d - RMT Transmission failed.", pin); + } else { // transmit OK + if (loop) { + bus->rmt_ch_is_looping = true; // for ever... until a channel canceling or new writing + } else { + if (blocking) { + // wait for transmission confirmation | timeout + retCode = (xEventGroupWaitBits(bus->rmt_events, RMT_FLAG_TX_DONE, pdFALSE /* do not clear on exit */, + pdFALSE /* wait for all bits */, timeout_ms) & RMT_FLAG_TX_DONE) != 0; + } + } } - int channel = rmt->channel; - RMT_MUTEX_LOCK(channel); - rmt_tx_stop(channel); - rmt_set_tx_loop_mode(channel, false); - rmt_write_items(channel, (const rmt_item32_t *)data, size, true); - RMT_MUTEX_UNLOCK(channel); - return true; + } + RMT_MUTEX_UNLOCK(bus); + return retCode; } -bool rmtReadData(rmt_obj_t* rmt, uint32_t* data, size_t size) +static bool _rmtRead(int pin, rmt_data_t* data, size_t *num_rmt_symbols, bool waitForData, uint32_t timeout_ms) { - if (!_rmtCheckTXnotRX(rmt, RMT_RX_MODE)) { - return false; - } - rmtReadAsync(rmt, (rmt_data_t*) data, size, NULL, false, 0); - return true; + rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__); + if (bus == NULL) { + return false; + } + if (!_rmtCheckDirection(pin, RMT_RX_MODE, __FUNCTION__)) { + return false; + } + if (data == NULL || num_rmt_symbols == NULL) { + log_w("GPIO %d - RMT Read Data and/or Size NULL pointer.", pin); + return false; + } + log_v("GPIO: %d - Request: %d RMT Symbols - %s - Timeout: %d", pin, *num_rmt_symbols, waitForData ? "Blocking" : "Non-Blocking", timeout_ms); + bool retCode = true; + RMT_MUTEX_LOCK(bus); + + // request reading RMT Channel Data + rmt_receive_config_t receive_config; + receive_config.signal_range_min_ns = bus->signal_range_min_ns; + receive_config.signal_range_max_ns = bus->signal_range_max_ns; + + xEventGroupClearBits(bus->rmt_events, RMT_FLAG_RX_DONE); + bus->num_symbols_read = num_rmt_symbols; + + rmt_receive(bus->rmt_channel_h, data, *num_rmt_symbols * sizeof(rmt_data_t), &receive_config); + // wait for data if requested + if (waitForData) { + retCode = (xEventGroupWaitBits(bus->rmt_events, RMT_FLAG_RX_DONE, pdFALSE /* do not clear on exit */, + pdFALSE /* wait for all bits */, timeout_ms) & RMT_FLAG_RX_DONE) != 0; + } + + RMT_MUTEX_UNLOCK(bus); + return retCode; } -bool rmtBeginReceive(rmt_obj_t* rmt) -{ - if (!_rmtCheckTXnotRX(rmt, RMT_RX_MODE)) { - return false; - } - int channel = rmt->channel; - - RMT_MUTEX_LOCK(channel); - rmt_set_memory_owner(channel, RMT_MEM_OWNER_RX); - rmt_rx_start(channel, true); - rmt->rx_completed = false; - RMT_MUTEX_UNLOCK(channel); - return true; +bool rmtWrite(int pin, rmt_data_t *data, size_t num_rmt_symbols, uint32_t timeout_ms) { + return _rmtWrite(pin, data, num_rmt_symbols, true /*blocks*/, false /*looping*/, timeout_ms); } -bool rmtReceiveCompleted(rmt_obj_t* rmt) -{ - if (!rmt) { - return false; - } - - return rmt->rx_completed; +bool rmtWriteAsync(int pin, rmt_data_t *data, size_t num_rmt_symbols) { + return _rmtWrite(pin, data, num_rmt_symbols, false /*blocks*/, false /*looping*/, 0 /*N/A*/); } -bool rmtRead(rmt_obj_t* rmt, rmt_rx_data_cb_t cb, void * arg) -{ - if (!_rmtCheckTXnotRX(rmt, RMT_RX_MODE)) { - return false; - } - int channel = rmt->channel; - - rmt->arg = arg; - rmt->cb = cb; - - RMT_MUTEX_LOCK(channel); - // cb as NULL is a way to cancel the callback process - if (cb == NULL) { - rmt_rx_stop(channel); - return true; - } - // Start a read process but now with a call back function - rmt_set_memory_owner(channel, RMT_MEM_OWNER_RX); - rmt_rx_start(channel, true); - rmt->rx_completed = false; - _rmtCreateRxTask(rmt); - RMT_MUTEX_UNLOCK(channel); - return true; +bool rmtWriteLooping(int pin, rmt_data_t* data, size_t num_rmt_symbols) { + return _rmtWrite(pin, data, num_rmt_symbols, false /*blocks*/, true /*looping*/, 0 /*N/A*/); } -bool rmtEnd(rmt_obj_t* rmt) -{ - if (!rmt) { - return false; - } - int channel = rmt->channel; - - RMT_MUTEX_LOCK(channel); - if (rmt->tx_not_rx) { - rmt_tx_stop(channel); - } else { - rmt_rx_stop(channel); - rmt->rx_completed = true; - } - RMT_MUTEX_UNLOCK(channel); - return true; +bool rmtTransmitCompleted(int pin) { + rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__); + if (bus == NULL) { + return false; + } + if (!_rmtCheckDirection(pin, RMT_TX_MODE, __FUNCTION__)) { + return false; + } + + bool retCode = true; + RMT_MUTEX_LOCK(bus); + retCode = (xEventGroupGetBits(bus->rmt_events) & RMT_FLAG_TX_DONE) != 0; + RMT_MUTEX_UNLOCK(bus); + return retCode; } -bool rmtReadAsync(rmt_obj_t* rmt, rmt_data_t* data, size_t size, void* eventFlag, bool waitForData, uint32_t timeout) -{ - if (!_rmtCheckTXnotRX(rmt, RMT_RX_MODE)) { - return false; - } - int channel = rmt->channel; - - // No limit on size with IDF ;-) - //if (g_rmt_objects[channel].buffers < size/SOC_RMT_MEM_WORDS_PER_CHANNEL) { - // return false; - //} +bool rmtRead(int pin, rmt_data_t* data, size_t *num_rmt_symbols, uint32_t timeout_ms) { + return _rmtRead(pin, data, num_rmt_symbols, true /* blocking */, timeout_ms); +} - RMT_MUTEX_LOCK(channel); - if (eventFlag) { - xEventGroupClearBits(eventFlag, RMT_FLAGS_ALL); - } - // if NULL, no problems - rmtReadAsync works as a plain rmtReadData() - rmt->events = eventFlag; - - // if NULL, no problems - task will take care of it - rmt->data_ptr = (uint32_t*)data; - rmt->data_size = size; - - // Start a read process - rmt_set_memory_owner(channel, RMT_MEM_OWNER_RX); - rmt_rx_start(channel, true); - rmt->rx_completed = false; - _rmtCreateRxTask(rmt); - RMT_MUTEX_UNLOCK(channel); - - // wait for data if requested so - if (waitForData && eventFlag) { - xEventGroupWaitBits(eventFlag, RMT_FLAGS_ALL, - pdTRUE /* clear on exit */, pdFALSE /* wait for all bits */, timeout); - } - return true; +bool rmtReadAsync(int pin, rmt_data_t* data, size_t *num_rmt_symbols) { + return _rmtRead(pin, data, num_rmt_symbols, false /* non-blocking */, 0 /* N/A */); } -float rmtSetTick(rmt_obj_t* rmt, float tick) -{ - if (!rmt) { - return false; - } - size_t channel = rmt->channel; - - RMT_MUTEX_LOCK(channel); - // RMT_BASECLK_REF (1MHz) is not supported in IDF upon Programmming Guide - // Only APB works - rmt_set_source_clk(channel, RMT_BASECLK_APB); - int apb_div = _LIMIT(tick/12.5f, 256); - float apb_tick = 12.5f * apb_div; - - rmt_set_clk_div(channel, apb_div & 0xFF); - RMT_MUTEX_UNLOCK(channel); - return apb_tick; +bool rmtReceiveCompleted(int pin) { + rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__); + if (bus == NULL) { + return false; + } + if (!_rmtCheckDirection(pin, RMT_RX_MODE, __FUNCTION__)) { + return false; + } + + bool retCode = true; + RMT_MUTEX_LOCK(bus); + retCode = (xEventGroupGetBits(bus->rmt_events) & RMT_FLAG_RX_DONE) != 0; + RMT_MUTEX_UNLOCK(bus); + return retCode; } -rmt_obj_t* rmtInit(int pin, bool tx_not_rx, rmt_reserve_memsize_t memsize) +bool rmtInit(int pin, rmt_ch_dir_t channel_direction, rmt_reserve_memsize_t mem_size, uint32_t frequency_Hz) { - uint32_t buffers = memsize; - rmt_obj_t* rmt = NULL; - uint32_t i = 0; - uint32_t j = 0; - - // create common block mutex for protecting allocs from multiple threads - if (!g_rmt_block_lock) { - g_rmt_block_lock = xSemaphoreCreateMutex(); - } - // lock - while (xSemaphoreTake(g_rmt_block_lock, portMAX_DELAY) != pdPASS) {} - - // Some SoC may have fixed channel numbers for TX and RX - example: ESP32C3 - uint8_t ch_start, ch_end; - if (tx_not_rx) { - ch_start = RMT_TX_CH_START; - ch_end = RMT_TX_CH_END; - } else { - ch_start = RMT_RX_CH_START; - ch_end = RMT_RX_CH_END; - } - for (i=ch_start; i<=ch_end; i++) { - for (j=0; j MAX_CHANNELS || j != buffers) { - xSemaphoreGive(g_rmt_block_lock); - log_e("rmInit Failed - not enough channels"); - return NULL; + log_v("GPIO %d - %s - MemSize[%d] - Freq=%dHz", pin, channel_direction == RMT_RX_MODE ? "RX MODE" : "TX MODE", mem_size * RMT_SYMBOLS_PER_CHANNEL_BLOCK, frequency_Hz); + + // create common block mutex for protecting allocs from multiple threads allocating RMT channels + if (!g_rmt_block_lock) { + g_rmt_block_lock = xSemaphoreCreateMutex(); + if (g_rmt_block_lock == NULL) { + log_e("GPIO %d - Failed creating RMT Mutex.", pin); + return false; + } + } + + // set Peripheral Manager deInit Callback + perimanSetBusDeinit(ESP32_BUS_TYPE_RMT_TX, _rmtDetachBus); + perimanSetBusDeinit(ESP32_BUS_TYPE_RMT_RX, _rmtDetachBus); + + // check is pin is valid and in the right direction + if ((channel_direction == RMT_TX_MODE && !GPIO_IS_VALID_OUTPUT_GPIO(pin)) || (!GPIO_IS_VALID_GPIO(pin))) { + log_e("GPIO %d is not valid or can't be used for output in TX mode.", pin); + return false; + } + + // validate the RMT ticks by the requested frequency + // Based on 80Mhz using a divider of 8 bits (calculated as 1..256) + if (frequency_Hz > 80000000 || frequency_Hz < 312500) { + log_e("GPIO %d - Bad RMT frequency resolution. Must be between 312.5KHz to 80MHz.", pin); + return false; + } + + // Try to dettach any (Tx|Rx|Whatever) previous bus or just keep it as not attached + if (perimanGetPinBusType(pin) != ESP32_BUS_TYPE_INIT && !perimanSetPinBus(pin, ESP32_BUS_TYPE_INIT, NULL)) { + log_w("GPIO %d - Can't detach previous peripheral.", pin); + return false; + } + + // lock it + while (xSemaphoreTake(g_rmt_block_lock, portMAX_DELAY) != pdPASS) {} + + // allocate the rmt bus object and sets all fields to NULL + rmt_bus_handle_t bus = (rmt_bus_handle_t)heap_caps_calloc(1, sizeof(struct rmt_obj_s), MALLOC_CAP_DEFAULT); + if (bus == NULL) { + log_e("GPIO %d - Bus Memory allocation fault.", pin); + goto Err; + } + + // pulses with width smaller than min_ns will be ignored (as a glitch) + bus->signal_range_min_ns = 1000000000 / (frequency_Hz * 2); // 1/2 pulse width + // RMT stops reading if the input stays idle for longer than max_ns + bus->signal_range_max_ns = (1000000000 / frequency_Hz) * 10; // 10 pulses width + // creates the event group to control read_done and write_done + bus->rmt_events = xEventGroupCreate(); + if (bus->rmt_events == NULL) { + log_e("GPIO %d - RMT Group Event allocation fault.", pin); + goto Err; + } + + // Starting with Receive|Transmit DONE bits set, for allowing a new request from user + xEventGroupSetBits(bus->rmt_events, RMT_FLAG_RX_DONE | RMT_FLAG_TX_DONE); + + // channel particular configuration + if (channel_direction == RMT_TX_MODE) { + // TX Channel + rmt_tx_channel_config_t tx_cfg; + tx_cfg.gpio_num = pin; + tx_cfg.clk_src = RMT_CLK_SRC_APB; + tx_cfg.resolution_hz = frequency_Hz; + tx_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL * mem_size; + tx_cfg.trans_queue_depth = 10; // maximum allowed + tx_cfg.flags.invert_out = 0; + tx_cfg.flags.with_dma = 0; + tx_cfg.flags.io_loop_back = 0; + tx_cfg.flags.io_od_mode = 0; + + if (rmt_new_tx_channel(&tx_cfg, &bus->rmt_channel_h) != ESP_OK) { + log_e("GPIO %d - RMT TX Initialization error.", pin); + goto Err; + } + + // set TX Callback + rmt_tx_event_callbacks_t cbs = { .on_trans_done = _rmt_tx_done_callback }; + if (ESP_OK != rmt_tx_register_event_callbacks(bus->rmt_channel_h, &cbs, bus)) { + log_e("GPIO %d RMT - Error registering TX Callback.", pin); + goto Err; + } + + } else { + // RX Channel + rmt_rx_channel_config_t rx_cfg; + rx_cfg.gpio_num = pin; + rx_cfg.clk_src = RMT_CLK_SRC_APB; + rx_cfg.resolution_hz = frequency_Hz; + rx_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL * mem_size; + rx_cfg.flags.invert_in = 0; + rx_cfg.flags.with_dma = 0; + rx_cfg.flags.io_loop_back = 0; + // try to allocate the RMT Channel + if (ESP_OK != rmt_new_rx_channel(&rx_cfg, &bus->rmt_channel_h)) { + log_e("GPIO %d RMT - RX Initialization error.", pin); + goto Err; } - // A suitable channel has been found, it has to block its resources in our internal data strucuture - size_t channel = i; - rmt = _rmtAllocate(pin, i, buffers); - - xSemaphoreGive(g_rmt_block_lock); - - rmt->buffers = buffers; - rmt->channel = channel; - rmt->arg = NULL; - rmt->cb = NULL; - rmt->data_ptr = NULL; - rmt->data_size = 0; - rmt->rx_completed = false; - rmt->events = NULL; - rmt->tx_not_rx = tx_not_rx; - + // set RX Callback + rmt_rx_event_callbacks_t cbs = { .on_recv_done = _rmt_rx_done_callback }; + if (ESP_OK != rmt_rx_register_event_callbacks(bus->rmt_channel_h, &cbs, bus)) { + log_e("GPIO %d RMT - Error registering RX Callback.", pin); + goto Err; + } + } + + // allocate memory for the RMT Copy encoder + rmt_copy_encoder_config_t copy_encoder_config = {}; + if (rmt_new_copy_encoder(©_encoder_config, &bus->rmt_copy_encoder_h) != ESP_OK) { + log_e("GPIO %d - RMT Encoder Memory Allocation error.", pin); + goto Err; + } + + // create each channel Mutex for multi thread operations #if !CONFIG_DISABLE_HAL_LOCKS - if(g_rmt_objlocks[channel] == NULL) { - g_rmt_objlocks[channel] = xSemaphoreCreateMutex(); - if(g_rmt_objlocks[channel] == NULL) { - return NULL; - } - } + bus->g_rmt_objlocks = xSemaphoreCreateMutex(); + if (bus->g_rmt_objlocks == NULL) { + log_e("GPIO %d - Failed creating RMT Channel Mutex.", pin); + goto Err; + } #endif - RMT_MUTEX_LOCK(channel); - esp_err_t esp_err_code = ESP_OK; - - if (tx_not_rx) { - rmt_config_t config = RMT_DEFAULT_ARD_CONFIG_TX(pin, channel, buffers); - esp_err_code = rmt_config(&config); - if (esp_err_code == ESP_OK) - esp_err_code = rmt_driver_install(channel, 0, 0); - log_d(" -- %s RMT - CH %d - %d RAM Blocks - pin %d", tx_not_rx?"TX":"RX", channel, buffers, pin); - } else { - rmt_config_t config = RMT_DEFAULT_ARD_CONFIG_RX(pin, channel, buffers); - esp_err_code = rmt_config(&config); - if (esp_err_code == ESP_OK) - esp_err_code = rmt_driver_install(channel, 1024, 0); - if (esp_err_code == ESP_OK) - esp_err_code = rmt_set_memory_owner(channel, RMT_MEM_OWNER_RX); - log_d(" -- %s RMT - CH %d - %d RAM Blocks - pin %d", tx_not_rx?"TX":"RX", channel, buffers, pin); - } - - RMT_MUTEX_UNLOCK(channel); - - if (esp_err_code == ESP_OK) { - return rmt; - } else { - log_e("RMT failed to initilize."); - return NULL; - } + rmt_enable(bus->rmt_channel_h); // starts/enables the channel + + // Finally, allocate Peripheral Manager RMT bus and associate it to its GPIO + peripheral_bus_type_t pinBusType = + channel_direction == RMT_TX_MODE ? ESP32_BUS_TYPE_RMT_TX : ESP32_BUS_TYPE_RMT_RX; + if (!perimanSetPinBus(pin, pinBusType, (void *) bus)) { + log_e("Can't allocate the GPIO %d in the Peripheral Manager.", pin); + goto Err; + } + + // this delay is necessary when CPU frequency changes, but internal RMT setup is "old/wrong" + // The use case is related to the RMT_CPUFreq_Test example. The very first RMT Write + // goes in the wrong pace (frequency). The delay allows other IDF tasks to run to fix it. + if (loopTaskHandle != NULL) { + // it can only run when Arduino task has been already started. + delay(1); + } // prevent panic when rmtInit() is executed within an C++ object constructor + // release the mutex + xSemaphoreGive(g_rmt_block_lock); + return true; + +Err: + // release LOCK and the RMT object + xSemaphoreGive(g_rmt_block_lock); + _rmtDetachBus((void *)bus); + return false; } diff --git a/cores/esp32/esp32-hal-rmt.h b/cores/esp32/esp32-hal-rmt.h index 201fe594009..a968d471377 100644 --- a/cores/esp32/esp32-hal-rmt.h +++ b/cores/esp32/esp32-hal-rmt.h @@ -1,4 +1,4 @@ -// Copyright 2018 Espressif Systems (Shanghai) PTE LTD +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -19,151 +19,199 @@ extern "C" { #endif -// notification flags -#define RMT_FLAG_TX_DONE (1) -#define RMT_FLAG_RX_DONE (2) -#define RMT_FLAG_ERROR (4) -#define RMT_FLAGS_ALL (RMT_FLAG_TX_DONE | RMT_FLAG_RX_DONE | RMT_FLAG_ERROR) - -#define RMT_TX_MODE true -#define RMT_RX_MODE false - -struct rmt_obj_s; +typedef enum { + RMT_RX_MODE = 0, // false + RMT_TX_MODE = 1, // true +} rmt_ch_dir_t; typedef enum { - RMT_MEM_64 = 1, - RMT_MEM_128 = 2, - RMT_MEM_192 = 3, - RMT_MEM_256 = 4, - RMT_MEM_320 = 5, - RMT_MEM_384 = 6, - RMT_MEM_448 = 7, - RMT_MEM_512 = 8, + RMT_MEM_NUM_BLOCKS_1 = 1, + RMT_MEM_NUM_BLOCKS_2 = 2, +#if SOC_RMT_TX_CANDIDATES_PER_GROUP > 2 + RMT_MEM_NUM_BLOCKS_3 = 3, + RMT_MEM_NUM_BLOCKS_4 = 4, +#if SOC_RMT_TX_CANDIDATES_PER_GROUP > 4 + RMT_MEM_NUM_BLOCKS_5 = 5, + RMT_MEM_NUM_BLOCKS_6 = 6, + RMT_MEM_NUM_BLOCKS_7 = 7, + RMT_MEM_NUM_BLOCKS_8 = 8, +#endif +#endif } rmt_reserve_memsize_t; -typedef struct rmt_obj_s rmt_obj_t; +// Each RMT Symbols has 4 bytes +// Total number of bytes per RMT_MEM_BLOCK is RMT_SYMBOLS_PER_CHANNEL_BLOCK * 4 bytes +typedef union { + struct { + uint32_t duration0 : 15; + uint32_t level0 : 1; + uint32_t duration1 : 15; + uint32_t level1 : 1; + }; + uint32_t val; +} rmt_data_t; -typedef void (*rmt_rx_data_cb_t)(uint32_t *data, size_t len, void *arg); +// Reading and Writing shall use as rmt_symbols_size this unit +// ESP32 has 8 MEM BLOCKS in total shared with Reading and/or Writing +// ESP32-S2 has 4 MEM BLOCKS in total shared with Reading and/or Writing +// ESP32-S3 has 4 MEM BLOCKS for Reading and another 4 MEM BLOCKS for Writing +// ESP32-C3 has 2 MEM BLOCKS for Reading and another 2 MEM BLOCKS for Writing +#define RMT_SYMBOLS_PER_CHANNEL_BLOCK SOC_RMT_MEM_WORDS_PER_CHANNEL -typedef struct { - union { - struct { - uint32_t duration0 :15; - uint32_t level0 :1; - uint32_t duration1 :15; - uint32_t level1 :1; - }; - uint32_t val; - }; -} rmt_data_t; +// Used to tell rmtRead() to wait for ever until reading data from the RMT channel +#define RMT_WAIT_FOR_EVER ((uint32_t)portMAX_DELAY) +// Helper macro to calculate the number of RTM symbols in a array or type +#define RMT_SYMBOLS_OF(x) (sizeof(x) / sizeof(rmt_data_t)) /** -* Prints object information -* + Initialize the object + + New Parameters in Arduino Core 3: RMT tick is set in the rmtInit() function by the + frequency of the RMT channel. Example: 100ns tick => 10MHz, thus frequency will be 10,000,000 Hz + Returns on execution success, otherwise */ -void _rmtDumpStatus(rmt_obj_t* rmt); +bool rmtInit(int pin, rmt_ch_dir_t channel_direction, rmt_reserve_memsize_t memsize, uint32_t frequency_Hz); /** -* Initialize the object -* + Sending data in Blocking Mode. + is a 32 bits structure as defined by rmt_data_t type. + It is possible to use the macro RMT_SYMBOLS_OF(data), if data is an array of . + + Blocking mode - only returns after sending all data or by timeout. + If the writing operation takes longer than in milliseconds, it will end its + execution returning . + Timeout can be set as undefined time by passing as parameter. + When the operation is timed out, rmtTransmitCompleted() will return until the transmission + is finished, when rmtTransmitCompleted() will return . + + Returns when there is no error in the write operation, otherwise, including when it + exits by timeout. */ -rmt_obj_t* rmtInit(int pin, bool tx_not_rx, rmt_reserve_memsize_t memsize); +bool rmtWrite(int pin, rmt_data_t *data, size_t num_rmt_symbols, uint32_t timeout_ms); /** -* Sets the clock/divider of timebase the nearest tick to the supplied value in nanoseconds -* return the real actual tick value in ns + Sending data in Async Mode. + is a 32 bits structure as defined by rmt_data_t type. + It is possible to use the macro RMT_SYMBOLS_OF(data), if is an array of + + If more than one rmtWriteAsync() is executed in sequence, it will wait for the first transmission + to finish, resulting in a return that indicates that the rmtWriteAsync() call has failed. + In such case, this channel will have to finish the previous transmission before starting a new one. + + Non-Blocking mode - returns right after execution. + Returns on execution success, otherwise. + + will return when all data is sent. */ -float rmtSetTick(rmt_obj_t* rmt, float tick); +bool rmtWriteAsync(int pin, rmt_data_t *data, size_t num_rmt_symbols); /** -* Sending data in one-go mode or continual mode -* (more data being send while updating buffers in interrupts) -* Non-Blocking mode - returns right after executing -*/ -bool rmtWrite(rmt_obj_t* rmt, rmt_data_t* data, size_t size); + Writing data up to the reserved memsize, looping continuously + is a 32 bits structure as defined by rmt_data_t type. + It is possible to use the macro RMT_SYMBOLS_OF(data), if data is an array of rmt_data_t -/** -* Sending data in one-go mode or continual mode -* (more data being send while updating buffers in interrupts) -* Blocking mode - only returns when data has been sent -*/ -bool rmtWriteBlocking(rmt_obj_t* rmt, rmt_data_t* data, size_t size); + If *data or size_byte are NULL | Zero, it will disable the writing loop and stop transmission -/** -* Loop data up to the reserved memsize continuously -* -*/ -bool rmtLoop(rmt_obj_t* rmt, rmt_data_t* data, size_t size); + Non-Blocking mode - returns right after execution + Returns on execution success, otherwise -/** -* Initiates async receive, event flag indicates data received -* -*/ -bool rmtReadAsync(rmt_obj_t* rmt, rmt_data_t* data, size_t size, void* eventFlag, bool waitForData, uint32_t timeout); + will return always while it is looping. +*/ +bool rmtWriteLooping(int pin, rmt_data_t* data, size_t num_rmt_symbols); /** -* Initiates async receive with automatic buffering -* and callback with data from ISR -* + Checks if transmission is completed and the rmtChannel ready for transmiting new data. + To be ready for a new transmission, means that the previous transmission is completed. + Returns when all data has been sent, otherwise. + The data transmition information is reset when a new rmtWrite/Async function is called. + If rmtWrite() times out or rmtWriteAsync() is called, this function will return until + all data is sent out. + rmtTranmitCompleted() will always return when rmtWriteLooping() is called, + beacuse it has no effect in such case. */ -bool rmtRead(rmt_obj_t* rmt, rmt_rx_data_cb_t cb, void * arg); - -/*** - * Ends async receive started with rmtRead(); but does not - * rmtDeInit(). - */ -bool rmtEnd(rmt_obj_t* rmt); - -/* Additional interface */ +bool rmtTransmitCompleted(int pin); /** -* Start reception -* + Initiates blocking receive. Read data will be stored in a user provided buffer <*data> + It will read up to RMT Symbols and the value of this variable will + change to the effective number of symbols read. + is a 32 bits structure as defined by rmt_data_t type. + + If the reading operation takes longer than in milliseconds, it will end its + execution and the function will return . In a time out scenario, won't + change and rmtReceiveCompleted() can be used latter to check if there is data available. + Timeout can be set as undefined time by passing RMT_WAIT_FOR_EVER as parameter + + Returns when there is no error in the read operation, otherwise, including when it + exits by timeout. + Returns, by value, the number of RMT Symbols read in and the user buffer + when the read operation has success within the defined . If the function times out, it + will read RMT data latter asynchronously, affecting <*data> and <*num_rmt_symbols>. After timeout, + the application can check if data is already available using */ -bool rmtBeginReceive(rmt_obj_t* rmt); +bool rmtRead(int pin, rmt_data_t* data, size_t *num_rmt_symbols, uint32_t timeout_ms); /** -* Checks if reception completes -* + Initiates async (non-blocking) receive. It will return immediately after execution. + Read data will be stored in a user provided buffer <*data>. + It will read up to RMT Symbols and the value of this variable will + change to the effective number of symbols read, whenever the read is completed. + is a 32 bits structure as defined by type. + + Returns when there is no error in the read operation, otherwise. + Returns asynchronously, by value, the number of RMT Symbols read, and also, it will copy + the RMT received data to the user buffer when the read operation happens. + The application can check if data is already available using */ -bool rmtReceiveCompleted(rmt_obj_t* rmt); +bool rmtReadAsync(int pin, rmt_data_t* data, size_t *num_rmt_symbols); /** -* Reads the data for particular channel -* + Checks if a data reception is completed and the rmtChannel has new data for processing. + Returns when data has been received, otherwise. + The data reception information is reset when a new rmtRead/Async function is called. */ -bool rmtReadData(rmt_obj_t* rmt, uint32_t* data, size_t size); +bool rmtReceiveCompleted(int pin); /** - * Setting threshold for Rx completed - */ -bool rmtSetRxThreshold(rmt_obj_t* rmt, uint32_t value); + Function used to set a threshold for the time used to consider that a data reception has ended. + In receive mode, when no edge is detected on the input signal for longer than idle_thres + channel clock cycles, the receiving process is finished and the Data is made available by + the rmtRead/Async functions. Note that this time (in RMT channel frequency cycles) will also + define how many low bits are read at the end of the received data. + The function returns if it is correctly executed, otherwise. +*/ +bool rmtSetRxThreshold(int pin, uint16_t value); /** - * Setting carrier - */ -bool rmtSetCarrier(rmt_obj_t* rmt, bool carrier_en, bool carrier_level, uint32_t low, uint32_t high); + Parameters changed in Arduino Core 3: low and high (ticks) are now expressed in Carrier Freq in Hz and + duty cycle in percentage float 0.0 to 1.0 - example: 38.5KHz 33% High => 38500, 0.33 + + Function to set a RX demodulation carrier or TX modulation carrier + is used to enable/disable the use of demodulation/modulation for RX/TX + true means that the polarity level for the (de)modulation is positive + is the carrier frequency used + is a float deom 0 to 1 (0.5 means a square wave) of the carrier frequency + The function returns if it is correctly executed, otherwise. +*/ +bool rmtSetCarrier(int pin, bool carrier_en, bool carrier_level, uint32_t frequency_Hz, float duty_percent); /** - * Setting input filter - */ -bool rmtSetFilter(rmt_obj_t* rmt, bool filter_en, uint32_t filter_level); + Function used to filter input noise in the RX channel. + In receiving mode, channel will ignore any input pulse which width is smaller than + If is Zero, it will to disable the filter. + The function returns if it is correctly executed, otherwise. +*/ +bool rmtSetFilter(int pin, uint8_t filter_level); /** - * Deinitialize the driver - */ -bool rmtDeinit(rmt_obj_t *rmt); - -// TODO: -// * uninstall interrupt when all channels are deinit -// * send only-conti mode with circular-buffer -// * put sanity checks to some macro or inlines -// * doxy comments -// * error reporting + Deinitializes the driver and releases all allocated memory + It also disables RMT for this gpio +*/ +bool rmtDeinit(int pin); #ifdef __cplusplus } #endif -#endif /* MAIN_ESP32_HAL_RMT_H_ */ +#endif /* MAIN_ESP32_HAL_RMT_H_ */ \ No newline at end of file diff --git a/libraries/ESP32/examples/RMT/RMTCallback/RMTCallback.ino b/libraries/ESP32/examples/RMT/RMTCallback/RMTCallback.ino index 61e6b55abcc..8a9a0ac27e0 100644 --- a/libraries/ESP32/examples/RMT/RMTCallback/RMTCallback.ino +++ b/libraries/ESP32/examples/RMT/RMTCallback/RMTCallback.ino @@ -1,67 +1,98 @@ -#include "Arduino.h" -#include "esp32-hal.h" +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @brief This example demonstrate how to use a C++ Class to read several GPIO RMT signals + * calling a data processor when data is availble in background, using taks. + * + * The output is the last RMT data read in the GPIO, just to ilustrate how it works. + * + */ -extern "C" void receive_trampoline(uint32_t *data, size_t len, void * arg); class MyProcessor { private: - rmt_obj_t* rmt_recv = NULL; - float realNanoTick; uint32_t buff; // rolling buffer of most recent 32 bits. int at = 0; + size_t rx_num_symbols = RMT_MEM_NUM_BLOCKS_1 * RMT_SYMBOLS_PER_CHANNEL_BLOCK; + rmt_data_t rx_symbols[RMT_MEM_NUM_BLOCKS_1 * RMT_SYMBOLS_PER_CHANNEL_BLOCK]; public: - MyProcessor(uint8_t pin, float nanoTicks) { - if ((rmt_recv = rmtInit(pin, RMT_RX_MODE, RMT_MEM_192)) == NULL) + int8_t gpio = -1; + + MyProcessor(uint8_t pin, uint32_t rmtFreqHz) { + if (!rmtInit(pin, RMT_RX_MODE, RMT_MEM_NUM_BLOCKS_1, rmtFreqHz)) { Serial.println("init receiver failed\n"); + return; } + gpio = pin; + } - realNanoTick = rmtSetTick(rmt_recv, nanoTicks); - }; void begin() { - rmtRead(rmt_recv, receive_trampoline, this); - }; + // Creating RMT RX Callback Task + xTaskCreate(readTask, "MyProcessor", 2048, this, 4, NULL); + } + + static void readTask(void *args) { + MyProcessor *me = (MyProcessor *) args; - void process(rmt_data_t *data, size_t len) { + while(1) { + // blocks until RMT has read data + rmtRead(me->gpio, me->rx_symbols, &me->rx_num_symbols, RMT_WAIT_FOR_EVER); + // process the data like a callback whenever there is data available + process(me->rx_symbols, me->rx_num_symbols, me); + } + vTaskDelete(NULL); + } + + static void process(rmt_data_t *data, size_t len, void *args) { + MyProcessor *me = (MyProcessor *) args; + uint32_t *buff = &me->buff; + for (int i = 0; len; len--) { if (data[i].duration0 == 0) break; - buff = (buff << 1) | (data[i].level0 ? 1 : 0); + *buff = (*buff << 1) | (data[i].level0 ? 1 : 0); i++; if (data[i].duration1 == 0) break; - buff = (buff << 1) | (data[i].level1 ? 1 : 0); + *buff = (*buff << 1) | (data[i].level1 ? 1 : 0); i++; }; - }; + } + uint32_t val() { return buff; } }; -void receive_trampoline(uint32_t *data, size_t len, void * arg) -{ - MyProcessor * p = (MyProcessor *)arg; - p->process((rmt_data_t*) data, len); -} - // Attach 3 processors to GPIO 4, 5 and 10 with different tick/speeds. -MyProcessor mp1 = MyProcessor(4, 1000); -MyProcessor mp2 = MyProcessor(5, 1000); -MyProcessor mp3 = MyProcessor(10, 500); +MyProcessor mp1 = MyProcessor(4, 1000000); +MyProcessor mp2 = MyProcessor(5, 1000000); +MyProcessor mp3 = MyProcessor(10, 2000000); -void setup() -{ +void setup() { Serial.begin(115200); mp1.begin(); mp2.begin(); mp3.begin(); } -void loop() -{ - Serial.printf("GPIO 4: %08lx 5: %08lx 10: %08lx\n", mp1.val(), mp2.val(), mp3.val()); +void loop() { + // The reading values will come from the 3 tasks started by setup() + Serial.printf("GPIO %d: %08lx | %d: %08lx | %d: %08lx\n", mp1.gpio, mp1.val(), mp2.gpio, mp2.val(), mp3.gpio, mp3.val()); delay(500); -} \ No newline at end of file +} diff --git a/libraries/ESP32/examples/RMT/RMTLoopback/RMTLoopback.ino b/libraries/ESP32/examples/RMT/RMTLoopback/RMTLoopback.ino index 851ced04131..b7469fc837a 100644 --- a/libraries/ESP32/examples/RMT/RMTLoopback/RMTLoopback.ino +++ b/libraries/ESP32/examples/RMT/RMTLoopback/RMTLoopback.ino @@ -1,74 +1,94 @@ -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/event_groups.h" -#include "Arduino.h" +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at -#include "esp32-hal.h" +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @brief This example demonstrates usage of RMT for testing a circuit loopback + * using 2 GPIOs, one for sending RMT data and the other for receiving the data. + * Those 2 GPIO must be connected to each other. + * + * The output is the RMT data comparing what was sent and received + * + */ #if CONFIG_IDF_TARGET_ESP32C3 // ESP32 C3 has only 2 channels for RX and 2 for TX, thus MAX RMT_MEM is 128 #define RMT_TX_PIN 4 #define RMT_RX_PIN 5 -#define RMT_MEM_RX RMT_MEM_128 +#define RMT_MEM_RX RMT_MEM_NUM_BLOCKS_2 #else #define RMT_TX_PIN 18 #define RMT_RX_PIN 21 -#define RMT_MEM_RX RMT_MEM_192 +#define RMT_MEM_RX RMT_MEM_NUM_BLOCKS_3 #endif rmt_data_t my_data[256]; rmt_data_t data[256]; -rmt_obj_t* rmt_send = NULL; -rmt_obj_t* rmt_recv = NULL; - static EventGroupHandle_t events; -void setup() -{ +#define RMT_FREQ 10000000 +#define RMT_NUM_EXCHANGED_DATA 30 + +void setup() { Serial.begin(115200); events = xEventGroupCreate(); - if ((rmt_send = rmtInit(RMT_TX_PIN, RMT_TX_MODE, RMT_MEM_64)) == NULL) - { - Serial.println("init sender failed\n"); + if (!rmtInit(RMT_TX_PIN, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, RMT_FREQ)) { + Serial.println("init sender failed\n"); } - if ((rmt_recv = rmtInit(RMT_RX_PIN, RMT_RX_MODE, RMT_MEM_RX)) == NULL) - { + if (!rmtInit(RMT_RX_PIN, RMT_RX_MODE, RMT_MEM_RX, RMT_FREQ)) { Serial.println("init receiver failed\n"); } - float realTick = rmtSetTick(rmt_send, 100); - printf("real tick set to: %fns\n", realTick); - // both will keep same tick - realTick = rmtSetTick(rmt_recv, 100); + // End of transmission shall be detected when line is idle for 2us + rmtSetRxThreshold(RMT_RX_PIN, 2000); + // Disable Glitch filter + rmtSetFilter(RMT_RX_PIN, 0); + + Serial.println("real tick set to: 100ns"); + Serial.printf("\nPlease connect GPIO %d to GPIO %d, now.\n", RMT_TX_PIN, RMT_RX_PIN); } -void loop() -{ +void loop() { // Init data int i; for (i=0; i<255; i++) { data[i].val = 0x80010001 + ((i%13)<<16) + 13-(i%13); + my_data[i].val = 0; } data[255].val = 0; - // Start receiving - rmtReadAsync(rmt_recv, my_data, 100, events, false, 0); + // Start an async data read + size_t rx_num_symbols = RMT_NUM_EXCHANGED_DATA; + rmtReadAsync(RMT_RX_PIN, my_data, &rx_num_symbols); - // Send in continous mode - rmtWrite(rmt_send, data, 100); - - // Wait for data - xEventGroupWaitBits(events, RMT_FLAG_RX_DONE, 1, 1, portMAX_DELAY); + // Write blocking the data to the loopback + rmtWrite(RMT_TX_PIN, data, RMT_NUM_EXCHANGED_DATA, RMT_WAIT_FOR_EVER); + // Wait until data is read + while (!rmtReceiveCompleted(RMT_RX_PIN)); + + // Once data is available, the number of RMT Symbols is stored in rx_num_symbols + // and the received data is copied to my_data + Serial.printf("Got %d RMT symbols\n", rx_num_symbols); + // Printout the received data plus the original values - for (i=0; i<60; i++) - { + for (i=0; i<60; i++) { Serial.printf("%08lx=%08lx ", my_data[i].val, data[i].val ); - if (!((i+1)%4)) Serial.println("\n"); + if (!((i+1)%4)) Serial.println(""); } Serial.println("\n"); - delay(2000); + delay(500); } diff --git a/libraries/ESP32/examples/RMT/RMTReadXJT/RMTReadXJT.ino b/libraries/ESP32/examples/RMT/RMTReadXJT/RMTReadXJT.ino index 1dcd8542da1..78e7e0b54cf 100644 --- a/libraries/ESP32/examples/RMT/RMTReadXJT/RMTReadXJT.ino +++ b/libraries/ESP32/examples/RMT/RMTReadXJT/RMTReadXJT.ino @@ -1,13 +1,27 @@ -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/event_groups.h" -#include "Arduino.h" +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at -#include "esp32-hal.h" +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @brief This example demonstrates usage of RMT for receiving XJT D12 data + * + * The output is the RMT data read and processed + * + */ // -// Note: This example uses a FrSKY device communication -// using XJT D12 protocol +// Note: This example uses a FrSKY device communication +// using XJT D12 protocol // // ; 0 bit = 6us low/10us high // ; 1 bit = 14us low/10us high @@ -24,7 +38,7 @@ // ; | 16us | 24us | // Typedef of received frame -// +// // ; 0x00 - Sync, 0x7E (sync header ID) // ; 0x01 - Rx ID, 0x?? (receiver ID number, 0x00-0x??) // ; 0x02 - Flags 1, 0x?? (used for failsafe and binding) @@ -38,166 +52,167 @@ // ; 0x12 - CRC-16 Low // ; 0x13 - Tail, 0x7E (tail ID) typedef union { - struct { - uint8_t head;//0x7E - uint8_t rxid;//Receiver Number - uint8_t flags;//Range:0x20, Bind:0x01 - uint8_t reserved0;//0x00 - union { - struct { - uint8_t ch0_l; - uint8_t ch0_h:4; - uint8_t ch1_l:4; - uint8_t ch1_h; - }; - uint8_t bytes[3]; - } channels[4]; - uint8_t reserved1;//0x00 - uint8_t crc_h; - uint8_t crc_l; - uint8_t tail;//0x7E - }; - uint8_t buffer[20]; + struct { + uint8_t head;//0x7E + uint8_t rxid;//Receiver Number + uint8_t flags;//Range:0x20, Bind:0x01 + uint8_t reserved0;//0x00 + union { + struct { + uint8_t ch0_l; + uint8_t ch0_h: 4; + uint8_t ch1_l: 4; + uint8_t ch1_h; + }; + uint8_t bytes[3]; + } channels[4]; + uint8_t reserved1;//0x00 + uint8_t crc_h; + uint8_t crc_l; + uint8_t tail;//0x7E + }; + uint8_t buffer[20]; } xjt_packet_t; #define XJT_VALID(i) (i->level0 && !i->level1 && i->duration0 >= 8 && i->duration0 <= 11) -rmt_obj_t* rmt_recv = NULL; - static uint32_t *s_channels; static uint32_t channels[16]; static uint8_t xjt_flags = 0x0; static uint8_t xjt_rxid = 0x0; -static bool xjtReceiveBit(size_t index, bool bit){ - static xjt_packet_t xjt; - static uint8_t xjt_bit_index = 8; - static uint8_t xht_byte_index = 0; - static uint8_t xht_ones = 0; - - if(!index){ - xjt_bit_index = 8; - xht_byte_index = 0; - xht_ones = 0; +static bool xjtReceiveBit(size_t index, bool bit) { + static xjt_packet_t xjt; + static uint8_t xjt_bit_index = 8; + static uint8_t xht_byte_index = 0; + static uint8_t xht_ones = 0; + + if (!index) { + xjt_bit_index = 8; + xht_byte_index = 0; + xht_ones = 0; + } + + if (xht_byte_index > 19) { + //fail! + return false; + } + if (bit) { + xht_ones++; + if (xht_ones > 5 && xht_byte_index && xht_byte_index < 19) { + //fail! + return false; } - - if(xht_byte_index > 19){ + //add bit + xjt.buffer[xht_byte_index] |= (1 << --xjt_bit_index); + } else if (xht_ones == 5 && xht_byte_index && xht_byte_index < 19) { + xht_ones = 0; + //skip bit + return true; + } else { + xht_ones = 0; + //add bit + xjt.buffer[xht_byte_index] &= ~(1 << --xjt_bit_index); + } + if ((!xjt_bit_index) || (xjt_bit_index == 1 && xht_byte_index == 19) ) { + xjt_bit_index = 8; + if (!xht_byte_index && xjt.buffer[0] != 0x7E) { + //fail! + return false; + } + xht_byte_index++; + if (xht_byte_index == 20) { + //done + if (xjt.buffer[19] != 0x7E) { //fail! return false; - } - if(bit){ - xht_ones++; - if(xht_ones > 5 && xht_byte_index && xht_byte_index < 19){ - //fail! - return false; - } - //add bit - xjt.buffer[xht_byte_index] |= (1 << --xjt_bit_index); - } else if(xht_ones == 5 && xht_byte_index && xht_byte_index < 19){ - xht_ones = 0; - //skip bit - return true; - } else { - xht_ones = 0; - //add bit - xjt.buffer[xht_byte_index] &= ~(1 << --xjt_bit_index); - } - if ((!xjt_bit_index) || (xjt_bit_index==1 && xht_byte_index==19) ) { - xjt_bit_index = 8; - if(!xht_byte_index && xjt.buffer[0] != 0x7E){ - //fail! - return false; + } + //check crc? + + xjt_flags = xjt.flags; + xjt_rxid = xjt.rxid; + for (int i = 0; i < 4; i++) { + uint16_t ch0 = xjt.channels[i].ch0_l | ((uint16_t)(xjt.channels[i].ch0_h & 0x7) << 8); + ch0 = ((ch0 * 2) + 2452) / 3; + uint16_t ch1 = xjt.channels[i].ch1_l | ((uint16_t)(xjt.channels[i].ch1_h & 0x7F) << 4); + ch1 = ((ch1 * 2) + 2452) / 3; + uint8_t c0n = i * 2; + if (xjt.channels[i].ch0_h & 0x8) { + c0n += 8; } - xht_byte_index++; - if(xht_byte_index == 20){ - //done - if(xjt.buffer[19] != 0x7E){ - //fail! - return false; - } - //check crc? - - xjt_flags = xjt.flags; - xjt_rxid = xjt.rxid; - for(int i=0; i<4; i++){ - uint16_t ch0 = xjt.channels[i].ch0_l | ((uint16_t)(xjt.channels[i].ch0_h & 0x7) << 8); - ch0 = ((ch0 * 2) + 2452) / 3; - uint16_t ch1 = xjt.channels[i].ch1_l | ((uint16_t)(xjt.channels[i].ch1_h & 0x7F) << 4); - ch1 = ((ch1 * 2) + 2452) / 3; - uint8_t c0n = i*2; - if(xjt.channels[i].ch0_h & 0x8){ - c0n += 8; - } - uint8_t c1n = i*2+1; - if(xjt.channels[i].ch1_h & 0x80){ - c1n += 8; - } - s_channels[c0n] = ch0; - s_channels[c1n] = ch1; - } + uint8_t c1n = i * 2 + 1; + if (xjt.channels[i].ch1_h & 0x80) { + c1n += 8; } + s_channels[c0n] = ch0; + s_channels[c1n] = ch1; + } } - return true; + } + return true; } -void parseRmt(rmt_data_t* items, size_t len, uint32_t* channels){ - bool valid = true; - rmt_data_t* it = NULL; +void parseRmt(rmt_data_t* items, size_t len, uint32_t* channels) { + bool valid = true; + rmt_data_t* it = NULL; - if (!channels) { - log_e("Please provide data block for storing channel info"); - return; - } - s_channels = channels; + if (!channels) { + log_e("Please provide data block for storing channel info"); + return; + } + s_channels = channels; - it = &items[0]; - for(size_t i = 0; iduration1 >= 5 && it->duration1 <= 8){ - valid = xjtReceiveBit(i, false); - } else if(it->duration1 >= 13 && it->duration1 <= 16){ - valid = xjtReceiveBit(i, true); - } else { - valid = false; - } - } else if(!it->duration1 && !it->level1 && it->duration0 >= 5 && it->duration0 <= 8) { - valid = xjtReceiveBit(i, false); + it = &items[0]; + for (size_t i = 0; i < len; i++) { - } + if (!valid) { + break; } -} + it = &items[i]; + if (XJT_VALID(it)) { + if (it->duration1 >= 5 && it->duration1 <= 8) { + valid = xjtReceiveBit(i, false); + } else if (it->duration1 >= 13 && it->duration1 <= 16) { + valid = xjtReceiveBit(i, true); + } else { + valid = false; + } + } else if (!it->duration1 && !it->level1 && it->duration0 >= 5 && it->duration0 <= 8) { + valid = xjtReceiveBit(i, false); -extern "C" void receive_data(uint32_t *data, size_t len, void * arg) -{ - parseRmt((rmt_data_t*) data, len, channels); -} - -void setup() -{ - Serial.begin(115200); - - // Initialize the channel to capture up to 192 items - if ((rmt_recv = rmtInit(21, RMT_RX_MODE, RMT_MEM_192)) == NULL) - { - Serial.println("init receiver failed\n"); } + } +} - // Setup 1us tick - float realTick = rmtSetTick(rmt_recv, 1000); - Serial.printf("real tick set to: %fns\n", realTick); +// Change the RMT reading GPIO here: +#define RMT_GPIO 21 - // Ask to start reading - rmtRead(rmt_recv, receive_data, NULL); +void setup() { + Serial.begin(115200); + // Initialize the channel to capture up to 64*2 or 48*2 items - 1us tick + if (!rmtInit(RMT_GPIO, RMT_RX_MODE, RMT_MEM_NUM_BLOCKS_2, 1000000)) { + Serial.println("init receiver failed\n"); + } + Serial.println("real tick set to: 1us"); } -void loop() -{ - // printout some of the channels - Serial.printf("%04lx %04lx %04lx %04lx\n", channels[0], channels[1], channels[2], channels[3]); - delay(500); +void loop() { + static rmt_data_t data[RMT_MEM_NUM_BLOCKS_2 * RMT_SYMBOLS_PER_CHANNEL_BLOCK]; + static size_t data_symbols = RMT_MEM_NUM_BLOCKS_2 * RMT_SYMBOLS_PER_CHANNEL_BLOCK; + + // Blocking read with timeout of 500ms + // If data is read, data_symbols will have the number of RMT symbols effectively read + // to check if something was read and it didn't just timeout, use rmtReceiveCompleted() + rmtRead(RMT_GPIO, data, &data_symbols, 500); + + // If read something, process the data + if (rmtReceiveCompleted(RMT_GPIO)) { + Serial.printf("Got %d RMT Symbols. Parsing data...\n", data_symbols); + parseRmt(data, data_symbols, channels); + } else { + Serial.println("No RMT data read..."); + } + + // printout some of the channels every 500ms + Serial.printf("%04lx %04lx %04lx %04lx\n", channels[0], channels[1], channels[2], channels[3]); } diff --git a/libraries/ESP32/examples/RMT/RMTWriteNeoPixel/RMTWriteNeoPixel.ino b/libraries/ESP32/examples/RMT/RMTWriteNeoPixel/RMTWriteNeoPixel.ino index 11f659f8895..9a381def79b 100644 --- a/libraries/ESP32/examples/RMT/RMTWriteNeoPixel/RMTWriteNeoPixel.ino +++ b/libraries/ESP32/examples/RMT/RMTWriteNeoPixel/RMTWriteNeoPixel.ino @@ -1,15 +1,29 @@ -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "freertos/event_groups.h" -#include "Arduino.h" +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. -#include "esp32-hal.h" +/** + * @brief This example demonstrates usage of RGB LED driven by RMT + * + * The output is a visual WS2812 RGB LED color moving in a 8 x 4 LED matrix + * Parameters can be changed by the user. In a single LED circuit, it will just blink. + */ // The effect seen in ESP32C3, ESP32S2 and ESP32S3 is like a Blink of RGB LED #if CONFIG_IDF_TARGET_ESP32S2 #define BUILTIN_RGBLED_PIN 18 #elif CONFIG_IDF_TARGET_ESP32S3 -#define BUILTIN_RGBLED_PIN 48 +#define BUILTIN_RGBLED_PIN 48 // 48 or 38 #elif CONFIG_IDF_TARGET_ESP32C3 #define BUILTIN_RGBLED_PIN 8 #else @@ -46,27 +60,18 @@ rmt_data_t led_data[NR_OF_ALL_BITS]; -rmt_obj_t* rmt_send = NULL; - -void setup() -{ +void setup() { Serial.begin(115200); - - if ((rmt_send = rmtInit(BUILTIN_RGBLED_PIN, RMT_TX_MODE, RMT_MEM_64)) == NULL) - { + if (!rmtInit(BUILTIN_RGBLED_PIN, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000)) { Serial.println("init sender failed\n"); } - - float realTick = rmtSetTick(rmt_send, 100); - Serial.printf("real tick set to: %fns\n", realTick); - + Serial.println("real tick set to: 100ns"); } -int color[] = { 0x55, 0x11, 0x77 }; // RGB value +int color[] = { 0x55, 0x11, 0x77 }; // Green Red Blue values int led_index = 0; -void loop() -{ +void loop() { // Init data with only one led ON int led, col, bit; int i=0; @@ -92,9 +97,7 @@ void loop() if ((++led_index)>=NR_OF_LEDS) { led_index = 0; } - - // Send the data - rmtWrite(rmt_send, led_data, NR_OF_ALL_BITS); - + // Send the data and wait until it is done + rmtWrite(BUILTIN_RGBLED_PIN, led_data, NR_OF_ALL_BITS, RMT_WAIT_FOR_EVER); delay(100); } diff --git a/libraries/ESP32/examples/RMT/RMT_CPUFreq_Test/RMT_CPUFreq_Test.ino b/libraries/ESP32/examples/RMT/RMT_CPUFreq_Test/RMT_CPUFreq_Test.ino new file mode 100644 index 00000000000..8150dcc7069 --- /dev/null +++ b/libraries/ESP32/examples/RMT/RMT_CPUFreq_Test/RMT_CPUFreq_Test.ino @@ -0,0 +1,90 @@ +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @brief This example demonstrates usage of RGB LED driven by RMT to verify + * that RMT works on any CPU/APB Frequency. + * + * It uses an ESP32 Arduino builtin RGB NeoLED function based on RMT: + * void neopixelWrite(uint8_t pin, uint8_t red_val, uint8_t green_val, uint8_t blue_val) + * + * The output is a visual WS2812 RGB LED color change routine using each time a + * different CPU Frequency, just to ilustrate how it works. Serial output indicates + * information about the CPU Frequency while controlling the RGB LED using RMT. + */ + + +// Default DevKit RGB LED GPIOs: +#if CONFIG_IDF_TARGET_ESP32S2 +#define MY_LED_GPIO 18 +#elif CONFIG_IDF_TARGET_ESP32S3 +#define MY_LED_GPIO 48 // 48 or 38 +#elif CONFIG_IDF_TARGET_ESP32C3 +#define MY_LED_GPIO 8 +#else +#define MY_LED_GPIO 21 // Any, ESP32 has no RGB LED - depends on circuit setup +#endif + +// Set the correct GPIO to any necessary by changing RGB_LED_GPIO value +#define RGB_LED_GPIO MY_LED_GPIO // Any GPIO valid in the board + +// Change the RGB Brightness to any value from 0 (off) to 255 (max) +#define BRIGHTNESS 20 // Change color brightness (max 255) + +void setup() { + Serial.begin(115200); + delay(500); + Serial.printf("\nUsing GPIO %d attached to the RGB LED.\nInitial CPU setup:\n", RGB_LED_GPIO); + + Serial.printf("CPU Freq = %lu MHz\n", getCpuFrequencyMhz()); + Serial.printf("XTAL Freq = %lu MHz\n", getXtalFrequencyMhz()); + Serial.printf("APB Freq = %lu Hz\n", getApbFrequency()); +} + +void loop() { + const uint8_t cpufreqs[] = {240, 160, 80, 40, 20, 10}; + static uint8_t i = 0; + + setCpuFrequencyMhz(cpufreqs[i]); + // moves to the next CPU freq for the next loop + i = (i + 1) % sizeof(cpufreqs); + + // Changing the CPU Freq demands RMT to reset internals parameters setting it correctly + // This is fixed by reinitializing the RMT peripheral as done below + // 100ns RMT Tick for driving the NeoLED as in the code of esp32-hal-rgb-led.c (github) + rmtInit(RGB_LED_GPIO, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 10000000); + + // resets also UART to adapt to the new CPU Freq + Serial.updateBaudRate(115200); + Serial.printf("\n--changed CPU Frequency to %lu MHz\n", getCpuFrequencyMhz()); + + neopixelWrite(RGB_LED_GPIO, BRIGHTNESS, BRIGHTNESS, BRIGHTNESS); // White + Serial.println("White"); + delay(1000); + neopixelWrite(RGB_LED_GPIO, 0, 0, 0); // Off + Serial.println("Off"); + delay(1000); + neopixelWrite(RGB_LED_GPIO, BRIGHTNESS, 0, 0); // Red + Serial.println("Red"); + delay(1000); + neopixelWrite(RGB_LED_GPIO, 0, BRIGHTNESS, 0); // Green + Serial.println("Green"); + delay(1000); + neopixelWrite(RGB_LED_GPIO, 0, 0, BRIGHTNESS); // Blue + Serial.println("Blue"); + delay(1000); + neopixelWrite(RGB_LED_GPIO, 0, 0, 0); // Off + Serial.println("Off"); + delay(1000); +} diff --git a/libraries/ESP32/examples/RMT/RMT_LED_Blink/RMT_LED_Blink.ino b/libraries/ESP32/examples/RMT/RMT_LED_Blink/RMT_LED_Blink.ino new file mode 100644 index 00000000000..3232fb71459 --- /dev/null +++ b/libraries/ESP32/examples/RMT/RMT_LED_Blink/RMT_LED_Blink.ino @@ -0,0 +1,220 @@ +// Copyright 2023 Espressif Systems (Shanghai) PTE LTD +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at + +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +/** + * @brief This example demonstrate how to use RMT to just blink a regular LED (GPIO) + * It uses all the different RMT Writing APIs to blink the LED by hardware, not being + * necessary the regular Blink code in Arduino. + * + * The output is the Blinking LED in the GPIO and a serial output describing what is + * going on, along the execution. + * + * The circuit is just a LED and a resistor of 270 ohms connected to the GPIO + * GPIO ---> resistor 270 ohms ---> + LED - ---> GND + */ + +#define BLINK_GPIO 2 + +// RMT is at 400KHz with a 2.5us tick +// This RMT data sends a 0.5Hz pulse with 1s High and 1s Low signal +rmt_data_t blink_1s_rmt_data[] = { + // 400,000 x 2.5us = 1 second ON + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + // 400,000 x 2.5us = 1 second OFF + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + // Looping mode needs a Zero ending data to mark the EOF + {0, 0, 0, 0} +}; + +// RMT is at 400KHz with a 2.5us tick +// This RMT data sends a 1Hz pulse with 500ms High and 500ms Low signal +rmt_data_t blink_500ms_rmt_data[] = { + // 200,000 x 2.5us = 0.5 second ON + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + // 200,000 x 2.5us = 0.5 second OFF + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + // Looping mode needs a Zero ending data to mark the EOF + {0, 0, 0, 0} +}; + +// RMT is at 400KHz with a 2.5us tick +// This RMT data sends a 2Hz pulse with 250ms High and 250ms Low signal +rmt_data_t blink_250ms_rmt_data[] = { + // 100,000 x 2.5us = 0.25 second ON + {25000, 1, 25000, 1,}, + {25000, 1, 25000, 1,}, + // 100,000 x 2.5us = 0.25 second OFF + {25000, 0, 25000, 0,}, + {25000, 0, 25000, 0,}, + // Looping mode needs a Zero ending data to mark the EOF + {0, 0, 0, 0} +}; + +void RMT_Mixed_Write_Blink() { + Serial.println("===> rmtWriteLooping() to Blink the LED."); + Serial.println("Blinking at 1s on + 1s off :: 3 blinks"); + if (!rmtWriteLooping(BLINK_GPIO, blink_1s_rmt_data, RMT_SYMBOLS_OF(blink_1s_rmt_data))) { + Serial.println("===> rmtWriteLooping Blink 1s Error!"); + } + delay(6000); // blinking happens here, done by hardware! + + Serial.println("===> rmtWrite() (Blocking Mode) to Blink the LED."); + Serial.println("Blinking at 500ms on + 500ms off :: 4 blinks"); + for (uint8_t i = 0; i < 4; i++) { + if (!rmtWrite(BLINK_GPIO, blink_500ms_rmt_data, RMT_SYMBOLS_OF(blink_500ms_rmt_data) - 2, RMT_WAIT_FOR_EVER)) { + Serial.println("===> rmtWrite Blink 0.5s Error!"); + } + } + + Serial.println("===> rmtWriteAsync() (Non-Blocking Mode) to Blink the LED."); + Serial.println("Blinking at 250ms on + 250ms off :: 5 blinks"); + for (uint8_t i = 0; i < 5; i++) { + if (!rmtWriteAsync(BLINK_GPIO, blink_250ms_rmt_data, RMT_SYMBOLS_OF(blink_250ms_rmt_data) - 2)) { + Serial.println("===> rmtWrite Blink 0.25s Error!"); + } + // wait (blocks) until all the data is sent out + while (!rmtTransmitCompleted(BLINK_GPIO)); + } + Serial.println("Blinking OFF for 1 seconds"); + delay(1000); +} + +void RMT_Loop_Write_Blink() { + Serial.println("Using RMT Writing loop to blink an LED."); + Serial.println("Blinking at 1s on + 1s off :: 3 blinks"); + if (!rmtWriteLooping(BLINK_GPIO, blink_1s_rmt_data, RMT_SYMBOLS_OF(blink_1s_rmt_data))) { + Serial.println("===> rmtWriteLooping Blink 1s Error!"); + } + delay(6000); + Serial.println("Blinking at 500ms on + 500ms off :: 5 blinks"); + if (!rmtWriteLooping(BLINK_GPIO, blink_500ms_rmt_data, RMT_SYMBOLS_OF(blink_500ms_rmt_data))) { + Serial.println("===> rmtWriteLooping Blink 0.5s Error!"); + } + delay(5000); + Serial.println("Blinking at 250ms on + 250ms off :: 10 blinks"); + if (!rmtWriteLooping(BLINK_GPIO, blink_250ms_rmt_data, RMT_SYMBOLS_OF(blink_250ms_rmt_data))) { + Serial.println("===> rmtWriteLooping Blink 0.25s Error!"); + } + delay(5000); + Serial.println("Blinking OFF for 2 seconds"); + if (!rmtWriteLooping(BLINK_GPIO, NULL, 0)) { + Serial.println("===> rmtWriteLooping Blink OFF Error!"); + } + delay(2000); +} + +void RMT_Single_Write_Blocking_Blink() { + Serial.println("Using RMT Writing and its Completion to blink an LED."); + Serial.println("Blinking at 1s on + 1s off :: 2 blinks"); + for (uint8_t i = 0; i < 2; i++) { + if (!rmtWrite(BLINK_GPIO, blink_1s_rmt_data, RMT_SYMBOLS_OF(blink_1s_rmt_data) - 2, RMT_WAIT_FOR_EVER)) { + Serial.println("===> rmtWrite Blink 1s Error!"); + } + } + Serial.println("Blinking at 500ms on + 500ms off :: 4 blinks"); + for (uint8_t i = 0; i < 4; i++) { + if (!rmtWrite(BLINK_GPIO, blink_500ms_rmt_data, RMT_SYMBOLS_OF(blink_500ms_rmt_data) - 2, RMT_WAIT_FOR_EVER)) { + Serial.println("===> rmtWrite Blink 0.5s Error!"); + } + } + Serial.println("Blinking at 250ms on + 250ms off :: 8 blinks"); + for (uint8_t i = 0; i < 8; i++) { + if (!rmtWrite(BLINK_GPIO, blink_250ms_rmt_data, RMT_SYMBOLS_OF(blink_250ms_rmt_data) - 2, RMT_WAIT_FOR_EVER)) { + Serial.println("===> rmtWrite Blink 0.25s Error!"); + } + } + Serial.println("Blinking OFF for 3 seconds"); + delay(3000); +} + +void RMT_Write_Aync_Non_Blocking_Blink() { + Serial.println("Using RMT Async Writing and its Completion to blink an LED."); + Serial.println("Blinking at 1s on + 1s off :: 5 blinks"); + for (uint8_t i = 0; i < 5; i++) { + if (!rmtWriteAsync(BLINK_GPIO, blink_1s_rmt_data, RMT_SYMBOLS_OF(blink_1s_rmt_data) - 2)) { + Serial.println("===> rmtWrite Blink 1s Error!"); + } + // wait (blocks) until all the data is sent out + while (!rmtTransmitCompleted(BLINK_GPIO)); + } + Serial.println("Blinking at 500ms on + 500ms off :: 5 blinks"); + for (uint8_t i = 0; i < 5; i++) { + if (!rmtWriteAsync(BLINK_GPIO, blink_500ms_rmt_data, RMT_SYMBOLS_OF(blink_500ms_rmt_data) - 2)) { + Serial.println("===> rmtWrite Blink 0.5s Error!"); + } + // wait (blocks) until all the data is sent out + while (!rmtTransmitCompleted(BLINK_GPIO)); + } + Serial.println("Blinking at 250ms on + 250ms off :: 5 blinks"); + for (uint8_t i = 0; i < 5; i++) { + if (!rmtWriteAsync(BLINK_GPIO, blink_250ms_rmt_data, RMT_SYMBOLS_OF(blink_250ms_rmt_data) - 2)) { + Serial.println("===> rmtWrite Blink 0.25s Error!"); + } + // wait (blocks) until all the data is sent out + while (!rmtTransmitCompleted(BLINK_GPIO)); + } + Serial.println("Blinking OFF for 1 seconds"); + delay(1000); +} + +void setup() { + Serial.begin(115200); + Serial.println("Starting Blink testing..."); + Serial.flush(); + + // 1 RMT Block has 64 RMT_SYMBOLS (ESP32|ESP32S2) or 48 RMT_SYMBOLS (ESP32C3|ESP32S3) + if (!rmtInit(BLINK_GPIO, RMT_TX_MODE, RMT_MEM_NUM_BLOCKS_1, 400000)) { //2.5us tick + Serial.println("===> rmtInit Error!"); + } else { + Serial.println("===> rmtInit OK! Tick = 2.5us - OK for testing"); + } + Serial.println("\n======================================"); + Serial.println( "All set. Starting RMT testing Routine."); + Serial.println( "======================================\n"); + + RMT_Mixed_Write_Blink(); + Serial.println("End of Mixed Calls testing"); + delay(1000); + + Serial.println("\n==============================="); + Serial.println( "Starting a Blinking sequence..."); + Serial.println( "===============================\n"); +} + +void loop() { + RMT_Write_Aync_Non_Blocking_Blink(); + RMT_Loop_Write_Blink(); + RMT_Single_Write_Blocking_Blink(); + Serial.println("\nStarting OVER...\n"); +}