3.3.7
This commit is contained in:
@@ -0,0 +1,675 @@
|
||||
// Copyright 2024 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 "soc/soc_caps.h"
|
||||
|
||||
#if SOC_RMT_SUPPORTED
|
||||
#include "esp32-hal.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/rmt_tx.h"
|
||||
#include "driver/rmt_rx.h"
|
||||
#include "hal/rmt_ll.h"
|
||||
|
||||
#include "esp32-hal-rmt.h"
|
||||
#include "esp32-hal-periman.h"
|
||||
#include "esp_idf_version.h"
|
||||
|
||||
// Arduino Task Handle indicates if the Arduino Task has been started already
|
||||
extern TaskHandle_t loopTaskHandle;
|
||||
|
||||
// RMT Events
|
||||
#define RMT_FLAG_RX_DONE (1)
|
||||
#define RMT_FLAG_TX_DONE (2)
|
||||
|
||||
/**
|
||||
Internal macros
|
||||
*/
|
||||
|
||||
#if CONFIG_DISABLE_HAL_LOCKS
|
||||
#define RMT_MUTEX_LOCK(busptr)
|
||||
#define RMT_MUTEX_UNLOCK(busptr)
|
||||
#else
|
||||
#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 */
|
||||
|
||||
/**
|
||||
Typedefs for internal structures, 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
|
||||
rmt_reserve_memsize_t mem_size; // RMT Memory size
|
||||
uint32_t frequency_Hz; // RMT Frequency
|
||||
uint8_t rmt_EOT_Level; // RMT End of Transmission Level - default is LOW
|
||||
|
||||
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||
SemaphoreHandle_t g_rmt_objlocks; // Channel Semaphore Lock
|
||||
#endif /* CONFIG_DISABLE_HAL_LOCKS */
|
||||
};
|
||||
|
||||
typedef struct rmt_obj_s *rmt_bus_handle_t;
|
||||
|
||||
/**
|
||||
Internal variables used in RMT API
|
||||
*/
|
||||
static SemaphoreHandle_t g_rmt_block_lock = NULL;
|
||||
|
||||
/**
|
||||
Internal method (private) declarations
|
||||
*/
|
||||
|
||||
// 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) {
|
||||
BaseType_t high_task_wakeup = pdFALSE;
|
||||
rmt_bus_handle_t bus = (rmt_bus_handle_t)args;
|
||||
// sets the returning number of RMT symbols (32 bits) effectively read
|
||||
*bus->num_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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
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;
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// 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 (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
|
||||
}
|
||||
|
||||
static rmt_bus_handle_t _rmtGetBus(int pin, const char *labelFunc) {
|
||||
// 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);
|
||||
}
|
||||
|
||||
// Peripheral Manager detach callback
|
||||
static bool _rmtDetachBus(void *busptr) {
|
||||
// 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);
|
||||
|
||||
// release the mutex
|
||||
xSemaphoreGive(g_rmt_block_lock);
|
||||
return retCode;
|
||||
}
|
||||
|
||||
/**
|
||||
Public method definitions
|
||||
*/
|
||||
|
||||
bool rmtSetEOT(int pin, uint8_t EOT_Level) {
|
||||
rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
|
||||
if (bus == NULL) {
|
||||
return false;
|
||||
}
|
||||
if (!_rmtCheckDirection(pin, RMT_TX_MODE, __FUNCTION__)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bus->rmt_EOT_Level = EOT_Level > 0 ? 1 : 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rmtSetCarrier(int pin, bool carrier_en, bool carrier_level, uint32_t frequency_Hz, float duty_percent) {
|
||||
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;
|
||||
memset((void *)&carrier_cfg, 0, sizeof(rmt_carrier_config_t));
|
||||
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 rmtSetRxMinThreshold(int pin, uint8_t filter_pulse_ticks) {
|
||||
rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
|
||||
if (bus == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_rmtCheckDirection(pin, RMT_RX_MODE, __FUNCTION__)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t filter_pulse_ns = (1000000000 / bus->frequency_Hz) * filter_pulse_ticks;
|
||||
// RMT_LL_MAX_FILTER_VALUE is 255 for ESP32, S2, S3, C3, C6 and H2;
|
||||
// filter_pulse_ticks is 8 bits, thus it will not exceed 255
|
||||
#if 0 // for the future, in case some other SoC has different limit
|
||||
if (filter_pulse_ticks > RMT_LL_MAX_FILTER_VALUE) {
|
||||
log_e("filter_pulse_ticks is too big. Max = %d", RMT_LL_MAX_FILTER_VALUE);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
RMT_MUTEX_LOCK(bus);
|
||||
bus->signal_range_min_ns = filter_pulse_ns; // set zero to disable it
|
||||
RMT_MUTEX_UNLOCK(bus);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rmtSetRxMaxThreshold(int pin, uint16_t idle_thres_ticks) {
|
||||
rmt_bus_handle_t bus = _rmtGetBus(pin, __FUNCTION__);
|
||||
if (bus == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!_rmtCheckDirection(pin, RMT_RX_MODE, __FUNCTION__)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32_t idle_thres_ns = (1000000000 / bus->frequency_Hz) * idle_thres_ticks;
|
||||
// RMT_LL_MAX_IDLE_VALUE is 65535 for ESP32,S2 and 32767 for S3, C3, C6 and H2
|
||||
#if RMT_LL_MAX_IDLE_VALUE < 65535 // idle_thres_ticks is 16 bits anyway - save some bytes
|
||||
if (idle_thres_ticks > RMT_LL_MAX_IDLE_VALUE) {
|
||||
log_e("idle_thres_ticks is too big. Max = %ld", RMT_LL_MAX_IDLE_VALUE);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
RMT_MUTEX_LOCK(bus);
|
||||
bus->signal_range_max_ns = idle_thres_ns;
|
||||
RMT_MUTEX_UNLOCK(bus);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool rmtDeinit(int pin) {
|
||||
log_v("Deiniting RMT GPIO %d", pin);
|
||||
if (_rmtGetBus(pin, __FUNCTION__) != NULL) {
|
||||
// release all allocated data
|
||||
return perimanClearPinBus(pin);
|
||||
}
|
||||
log_e("GPIO %d - No RMT channel associated.", pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool _rmtWrite(int pin, rmt_data_t *data, size_t num_rmt_symbols, bool blocking, uint32_t loop, uint32_t timeout_ms) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
|
||||
log_v("GPIO: %d - Request: %d RMT Symbols - %s - Timeout: %d", pin, num_rmt_symbols, blocking ? "Blocking" : "Non-Blocking", timeout_ms);
|
||||
// loop parameter semantics:
|
||||
// loop == 0: no looping (single transmission)
|
||||
// loop == 1: infinite looping
|
||||
// loop > 1: transmit the data 'loop' times
|
||||
{
|
||||
char buf[17]; // placeholder for up to maximum uint32_t value (4294967295) = 10 digits + " times" (6 chars) + null terminator (17 bytes)
|
||||
snprintf(buf, sizeof(buf), "%lu times", loop);
|
||||
log_v(
|
||||
"GPIO: %d - Currently in Loop Mode: [%s] | Loop Request: [%s], LoopCancel: [%s]", pin, bus->rmt_ch_is_looping ? "YES" : "NO",
|
||||
loop == 0 ? "NO" : (loop == 1 ? "FOREVER" : buf), loopCancel ? "YES" : "NO"
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
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; // loop mode disabled
|
||||
memset((void *)&transmit_cfg, 0, sizeof(rmt_transmit_config_t));
|
||||
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
|
||||
}
|
||||
// sets the End of Transmission level to HIGH if the user has requested so
|
||||
if (bus->rmt_EOT_Level) {
|
||||
transmit_cfg.flags.eot_level = 1; // EOT is HIGH
|
||||
}
|
||||
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 > 0) {
|
||||
transmit_cfg.loop_count = (loop == 1) ? -1 : loop;
|
||||
// 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 > 0) {
|
||||
// rmt_ch_is_looping is used as a flag to indicate that RMT is in looping execution in order to
|
||||
// be canceled whenever a new _rmtWrite() is executed while it is looping
|
||||
bus->rmt_ch_is_looping = true;
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
RMT_MUTEX_UNLOCK(bus);
|
||||
return retCode;
|
||||
}
|
||||
|
||||
static bool _rmtRead(int pin, rmt_data_t *data, size_t *num_rmt_symbols, bool waitForData, uint32_t timeout_ms) {
|
||||
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;
|
||||
memset((void *)&receive_config, 0, sizeof(rmt_receive_config_t));
|
||||
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;
|
||||
|
||||
if (waitForData) {
|
||||
// resets the reading channel to start fresh
|
||||
rmt_disable(bus->rmt_channel_h);
|
||||
rmt_enable(bus->rmt_channel_h);
|
||||
}
|
||||
|
||||
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 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*/, 0 /*looping*/, timeout_ms);
|
||||
}
|
||||
|
||||
bool rmtWriteAsync(int pin, rmt_data_t *data, size_t num_rmt_symbols) {
|
||||
return _rmtWrite(pin, data, num_rmt_symbols, false /*blocks*/, 0 /*looping*/, 0 /*N/A*/);
|
||||
}
|
||||
|
||||
bool rmtWriteLooping(int pin, rmt_data_t *data, size_t num_rmt_symbols) {
|
||||
return _rmtWrite(pin, data, num_rmt_symbols, false /*blocks*/, 1 /*looping*/, 0 /*N/A*/);
|
||||
}
|
||||
|
||||
// Same as rmtWriteLooping(...) but it transmits the data a fixed number of times ("loop_count").
|
||||
// loop_count == 0 is invalid (no transmission); loop_count == 1 transmits once (no looping); loop_count > 1 transmits the data repeatedly (looping).
|
||||
bool rmtWriteRepeated(int pin, rmt_data_t *data, size_t num_rmt_symbols, uint32_t loop_count) {
|
||||
if (loop_count == 0) {
|
||||
log_e("RMT TX GPIO %d : Invalid loop_count (%u). Must be at least 1.", pin, loop_count);
|
||||
return false;
|
||||
}
|
||||
if (loop_count == 1) {
|
||||
// send the RMT symbols once using non-blocking write (single non-looping transmission)
|
||||
return _rmtWrite(pin, data, num_rmt_symbols, false /*blocks*/, 0 /*looping*/, 0 /*N/A*/);
|
||||
} else {
|
||||
// write the RMT symbols for loop_count times
|
||||
#if SOC_RMT_SUPPORT_TX_LOOP_COUNT
|
||||
return _rmtWrite(pin, data, num_rmt_symbols, false /*blocks*/, loop_count /*looping*/, 0 /*N/A*/);
|
||||
#else
|
||||
log_e("RMT TX GPIO %d : Loop Count is not supported. Writing failed.", pin);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
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 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);
|
||||
}
|
||||
|
||||
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 */);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool rmtInit(int pin, rmt_ch_dir_t channel_direction, rmt_reserve_memsize_t mem_size, uint32_t frequency_Hz) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// check if the RMT peripheral is already initialized with the same parameters
|
||||
rmt_bus_handle_t bus = NULL;
|
||||
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) {
|
||||
rmt_ch_dir_t bus_rmt_dir = rmt_bus_type == ESP32_BUS_TYPE_RMT_TX ? RMT_TX_MODE : RMT_RX_MODE;
|
||||
bus = (rmt_bus_handle_t)perimanGetPinBus(pin, rmt_bus_type);
|
||||
if (bus->frequency_Hz == frequency_Hz && bus_rmt_dir == channel_direction && bus->mem_size == mem_size) {
|
||||
return true; // already initialized with the same parameters
|
||||
}
|
||||
}
|
||||
|
||||
// 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 detach any (Tx|Rx|Whatever) previous bus or just keep it as not attached
|
||||
if (!perimanClearPinBus(pin)) {
|
||||
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
|
||||
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;
|
||||
}
|
||||
|
||||
// store the RMT Freq and mem_size to check Initialization, Filter and Idle valid values in the RMT API
|
||||
bus->frequency_Hz = frequency_Hz;
|
||||
bus->mem_size = mem_size;
|
||||
// pulses with width smaller than min_ns will be ignored (as a glitch)
|
||||
//bus->signal_range_min_ns = 0; // disabled --> not necessary CALLOC set all to ZERO.
|
||||
// RMT stops reading if the input stays idle for longer than max_ns
|
||||
bus->signal_range_max_ns = (1000000000 / frequency_Hz) * RMT_LL_MAX_IDLE_VALUE; // maximum possible
|
||||
// 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;
|
||||
memset((void *)&tx_cfg, 0, sizeof(rmt_tx_channel_config_t));
|
||||
tx_cfg.gpio_num = pin;
|
||||
// CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F80M for C6 -- CLK_XTAL for H2
|
||||
tx_cfg.clk_src = RMT_CLK_SRC_DEFAULT;
|
||||
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 ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2)
|
||||
tx_cfg.intr_priority = 0;
|
||||
#endif
|
||||
|
||||
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;
|
||||
memset((void *)&rx_cfg, 0, sizeof(rmt_rx_channel_config_t));
|
||||
rx_cfg.gpio_num = pin;
|
||||
// CLK_APB for ESP32|S2|S3|C3 -- CLK_PLL_F80M for C6 -- CLK_XTAL for H2
|
||||
rx_cfg.clk_src = RMT_CLK_SRC_DEFAULT;
|
||||
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;
|
||||
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 1, 2)
|
||||
rx_cfg.intr_priority = 0;
|
||||
#endif
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
memset((void *)©_encoder_config, 0, sizeof(rmt_copy_encoder_config_t));
|
||||
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
|
||||
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_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, -1, -1)) {
|
||||
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;
|
||||
}
|
||||
|
||||
#endif /* SOC_RMT_SUPPORTED */
|
||||
Reference in New Issue
Block a user