From 5bfe87372556bd7356cc1402313fe2e526e36f88 Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 7 Apr 2022 11:50:41 +0800 Subject: [PATCH 01/10] driver: deprecate legacy rmt driver The legacy driver is still available, but new feature won't be added. Enable RMT_SUPPRESS_DEPRECATE_WARN if you don't want to see the deprecated warnings. --- components/driver/Kconfig | 11 + .../{include => deprecated}/driver/rmt.h | 273 +---------------- .../deprecated/driver/rmt_types_legacy.h | 279 ++++++++++++++++++ .../driver/{rmt.c => deprecated/rmt_legacy.c} | 25 +- .../deprecated/rtc_temperature_legacy.c | 2 +- components/driver/test/CMakeLists.txt | 2 +- .../legacy_rmt_driver/CMakeLists.txt | 5 + .../test_apps/legacy_rmt_driver/README.md | 2 + .../components/infrared_tools/CMakeLists.txt | 9 +- .../infrared_tools/include/ir_timings.h | 35 +++ .../infrared_tools/include/ir_tools.h | 0 .../infrared_tools/src/ir_builder_rmt_nec.c | 18 +- .../infrared_tools/src/ir_builder_rmt_rc5.c | 18 +- .../infrared_tools/src/ir_parser_rmt_nec.c | 18 +- .../infrared_tools/src/ir_parser_rmt_rc5.c | 18 +- .../legacy_rmt_driver/main/CMakeLists.txt | 5 + .../legacy_rmt_driver/main/test_app_main.c | 40 +++ .../legacy_rmt_driver/main/test_legacy_rmt.c} | 6 +- .../pytest_legacy_rmt_driver.py | 19 ++ .../legacy_rmt_driver/sdkconfig.ci.release | 5 + .../legacy_rmt_driver/sdkconfig.defaults | 3 + .../infrared_tools/include/ir_timings.h | 43 --- tools/ci/check_copyright_ignore.txt | 5 - tools/unit-test-app/CMakeLists.txt | 3 +- .../test_utils/ref_clock_impl_rmt_pcnt.c | 2 +- 25 files changed, 458 insertions(+), 388 deletions(-) rename components/driver/{include => deprecated}/driver/rmt.h (71%) create mode 100644 components/driver/deprecated/driver/rmt_types_legacy.h rename components/driver/{rmt.c => deprecated/rmt_legacy.c} (98%) create mode 100644 components/driver/test_apps/legacy_rmt_driver/CMakeLists.txt create mode 100644 components/driver/test_apps/legacy_rmt_driver/README.md rename {examples/peripherals/rmt/ir_protocols => components/driver/test_apps/legacy_rmt_driver}/components/infrared_tools/CMakeLists.txt (56%) create mode 100644 components/driver/test_apps/legacy_rmt_driver/components/infrared_tools/include/ir_timings.h rename {examples/peripherals/rmt/ir_protocols => components/driver/test_apps/legacy_rmt_driver}/components/infrared_tools/include/ir_tools.h (100%) rename {examples/peripherals/rmt/ir_protocols => components/driver/test_apps/legacy_rmt_driver}/components/infrared_tools/src/ir_builder_rmt_nec.c (93%) rename {examples/peripherals/rmt/ir_protocols => components/driver/test_apps/legacy_rmt_driver}/components/infrared_tools/src/ir_builder_rmt_rc5.c (92%) rename {examples/peripherals/rmt/ir_protocols => components/driver/test_apps/legacy_rmt_driver}/components/infrared_tools/src/ir_parser_rmt_nec.c (92%) rename {examples/peripherals/rmt/ir_protocols => components/driver/test_apps/legacy_rmt_driver}/components/infrared_tools/src/ir_parser_rmt_rc5.c (90%) create mode 100644 components/driver/test_apps/legacy_rmt_driver/main/CMakeLists.txt create mode 100644 components/driver/test_apps/legacy_rmt_driver/main/test_app_main.c rename components/driver/{test/test_rmt.c => test_apps/legacy_rmt_driver/main/test_legacy_rmt.c} (99%) create mode 100644 components/driver/test_apps/legacy_rmt_driver/pytest_legacy_rmt_driver.py create mode 100644 components/driver/test_apps/legacy_rmt_driver/sdkconfig.ci.release create mode 100644 components/driver/test_apps/legacy_rmt_driver/sdkconfig.defaults delete mode 100644 examples/peripherals/rmt/ir_protocols/components/infrared_tools/include/ir_timings.h diff --git a/components/driver/Kconfig b/components/driver/Kconfig index 1b7cf24aeb..366e5ff831 100644 --- a/components/driver/Kconfig +++ b/components/driver/Kconfig @@ -279,4 +279,15 @@ menu "Driver configurations" Note that, this option only controls the PCNT driver log, won't affect other drivers. endmenu # PCNT Configuration + menu "RMT Configuration" + depends on SOC_RMT_SUPPORTED + config RMT_SUPPRESS_DEPRECATE_WARN + bool "Suppress legacy driver deprecated warning" + default n + help + Wether to suppress the deprecation warnings when using legacy rmt driver (driver/rmt.h). + If you want to continue using the legacy driver, and don't want to see related deprecation warnings, + you can enable this option. + endmenu # RMT Configuration + endmenu # Driver configurations diff --git a/components/driver/include/driver/rmt.h b/components/driver/deprecated/driver/rmt.h similarity index 71% rename from components/driver/include/driver/rmt.h rename to components/driver/deprecated/driver/rmt.h index 6d3aa286ac..c99bde31be 100644 --- a/components/driver/include/driver/rmt.h +++ b/components/driver/deprecated/driver/rmt.h @@ -6,281 +6,22 @@ #pragma once -#include #include +#include +#include "sdkconfig.h" #include "esp_err.h" -#include "soc/soc_caps.h" -#include "soc/clk_tree_defs.h" -#include "driver/gpio.h" #include "freertos/FreeRTOS.h" #include "freertos/ringbuf.h" -#include "hal/rmt_types.h" +#include "driver/rmt_types_legacy.h" + +#if !CONFIG_RMT_SUPPRESS_DEPRECATE_WARN +#warning "The legacy RMT driver is deprecated, please use driver/rmt_tx.h and/or driver/rmt_rx.h" +#endif #ifdef __cplusplus extern "C" { #endif -#define RMT_CHANNEL_FLAGS_AWARE_DFS (1 << 0) /*!< Channel can work during APB clock scaling */ -#define RMT_CHANNEL_FLAGS_INVERT_SIG (1 << 1) /*!< Invert RMT signal */ - -/** @cond */ -#define RMT_CHANNEL_FLAGS_ALWAYS_ON RMT_CHANNEL_FLAGS_AWARE_DFS /*!< Deprecated name, defined here for compatibility */ -/** @endcond */ - -/** - * @brief Define memory space of each RMT channel (in words = 4 bytes) - * - */ -#define RMT_MEM_ITEM_NUM SOC_RMT_MEM_WORDS_PER_CHANNEL - -/** - * @brief Definition of RMT item - */ -typedef struct { - union { - struct { - uint32_t duration0 : 15; /*!< Duration of level0 */ - uint32_t level0 : 1; /*!< Level of the first part */ - uint32_t duration1 : 15; /*!< Duration of level1 */ - uint32_t level1 : 1; /*!< Level of the second part */ - }; - uint32_t val; /*!< Equivalent unsigned value for the RMT item */ - }; -} rmt_item32_t; - -/** - * @brief RMT hardware memory layout - */ -typedef struct { - struct { - volatile rmt_item32_t data32[SOC_RMT_MEM_WORDS_PER_CHANNEL]; - } chan[SOC_RMT_CHANNELS_PER_GROUP]; -} rmt_mem_t; - -/** -* @brief RMT channel ID -* -*/ -typedef enum { - RMT_CHANNEL_0, /*!< RMT channel number 0 */ - RMT_CHANNEL_1, /*!< RMT channel number 1 */ - RMT_CHANNEL_2, /*!< RMT channel number 2 */ - RMT_CHANNEL_3, /*!< RMT channel number 3 */ -#if SOC_RMT_CHANNELS_PER_GROUP > 4 - RMT_CHANNEL_4, /*!< RMT channel number 4 */ - RMT_CHANNEL_5, /*!< RMT channel number 5 */ - RMT_CHANNEL_6, /*!< RMT channel number 6 */ - RMT_CHANNEL_7, /*!< RMT channel number 7 */ -#endif - RMT_CHANNEL_MAX /*!< Number of RMT channels */ -} rmt_channel_t; - -/** - * @brief RMT Internal Memory Owner - * - */ -typedef enum { - RMT_MEM_OWNER_TX, /*!< RMT RX mode, RMT transmitter owns the memory block*/ - RMT_MEM_OWNER_RX, /*!< RMT RX mode, RMT receiver owns the memory block*/ - RMT_MEM_OWNER_MAX, -} rmt_mem_owner_t; - -/** - * @brief Clock Source of RMT Channel - * - */ -typedef soc_periph_rmt_clk_src_legacy_t rmt_source_clk_t; - -/** - * @brief RMT Data Mode - * - * @note We highly recommended to use MEM mode not FIFO mode since there will be some gotcha in FIFO mode. - * - */ -typedef enum { - RMT_DATA_MODE_FIFO, /* +#include "soc/soc_caps.h" +#include "soc/clk_tree_defs.h" +#include "driver/gpio.h" + +#ifdef __cplusplus +extern "C" { +#endif + + +#define RMT_CHANNEL_FLAGS_AWARE_DFS (1 << 0) /*!< Channel can work during APB clock scaling */ +#define RMT_CHANNEL_FLAGS_INVERT_SIG (1 << 1) /*!< Invert RMT signal */ +#define RMT_CHANNEL_FLAGS_ALWAYS_ON _Pragma ("GCC warning \"'RMT_CHANNEL_FLAGS_ALWAYS_ON' macro is deprecated\"") RMT_CHANNEL_FLAGS_AWARE_DFS + +/** + * @brief Define memory space of each RMT channel (in words = 4 bytes) + * + */ +#define RMT_MEM_ITEM_NUM SOC_RMT_MEM_WORDS_PER_CHANNEL + +/** + * @brief Definition of RMT item + */ +typedef struct { + union { + struct { + uint32_t duration0 : 15; /*!< Duration of level0 */ + uint32_t level0 : 1; /*!< Level of the first part */ + uint32_t duration1 : 15; /*!< Duration of level1 */ + uint32_t level1 : 1; /*!< Level of the second part */ + }; + uint32_t val; /*!< Equivalent unsigned value for the RMT item */ + }; +} rmt_item32_t; + +/** + * @brief RMT hardware memory layout + */ +typedef struct { + struct { + volatile rmt_item32_t data32[SOC_RMT_MEM_WORDS_PER_CHANNEL]; + } chan[SOC_RMT_CHANNELS_PER_GROUP]; +} rmt_mem_t; + +/** +* @brief RMT channel ID +* +*/ +typedef enum { + RMT_CHANNEL_0, /*!< RMT channel number 0 */ + RMT_CHANNEL_1, /*!< RMT channel number 1 */ + RMT_CHANNEL_2, /*!< RMT channel number 2 */ + RMT_CHANNEL_3, /*!< RMT channel number 3 */ +#if SOC_RMT_CHANNELS_PER_GROUP > 4 + RMT_CHANNEL_4, /*!< RMT channel number 4 */ + RMT_CHANNEL_5, /*!< RMT channel number 5 */ + RMT_CHANNEL_6, /*!< RMT channel number 6 */ + RMT_CHANNEL_7, /*!< RMT channel number 7 */ +#endif + RMT_CHANNEL_MAX /*!< Number of RMT channels */ +} rmt_channel_t; + +/** + * @brief RMT Internal Memory Owner + * + */ +typedef enum { + RMT_MEM_OWNER_TX, /*!< RMT RX mode, RMT transmitter owns the memory block*/ + RMT_MEM_OWNER_RX, /*!< RMT RX mode, RMT receiver owns the memory block*/ + RMT_MEM_OWNER_MAX, +} rmt_mem_owner_t; + +/** + * @brief Clock Source of RMT Channel + * + */ +typedef soc_periph_rmt_clk_src_legacy_t rmt_source_clk_t; + +/** + * @brief RMT Data Mode + * + * @note We highly recommended to use MEM mode not FIFO mode since there will be some gotcha in FIFO mode. + * + */ +typedef enum { + RMT_DATA_MODE_FIFO, /* +/* + * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include "esp_log.h" #include "ir_tools.h" diff --git a/examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_builder_rmt_rc5.c b/components/driver/test_apps/legacy_rmt_driver/components/infrared_tools/src/ir_builder_rmt_rc5.c similarity index 92% rename from examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_builder_rmt_rc5.c rename to components/driver/test_apps/legacy_rmt_driver/components/infrared_tools/src/ir_builder_rmt_rc5.c index 7063961abf..9160779cea 100644 --- a/examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_builder_rmt_rc5.c +++ b/components/driver/test_apps/legacy_rmt_driver/components/infrared_tools/src/ir_builder_rmt_rc5.c @@ -1,16 +1,8 @@ -// Copyright 2019 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. +/* + * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include #include "esp_log.h" diff --git a/examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_parser_rmt_nec.c b/components/driver/test_apps/legacy_rmt_driver/components/infrared_tools/src/ir_parser_rmt_nec.c similarity index 92% rename from examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_parser_rmt_nec.c rename to components/driver/test_apps/legacy_rmt_driver/components/infrared_tools/src/ir_parser_rmt_nec.c index cddeabbad8..715997dabd 100644 --- a/examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_parser_rmt_nec.c +++ b/components/driver/test_apps/legacy_rmt_driver/components/infrared_tools/src/ir_parser_rmt_nec.c @@ -1,16 +1,8 @@ -// Copyright 2019 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. +/* + * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include #include "esp_log.h" diff --git a/examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_parser_rmt_rc5.c b/components/driver/test_apps/legacy_rmt_driver/components/infrared_tools/src/ir_parser_rmt_rc5.c similarity index 90% rename from examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_parser_rmt_rc5.c rename to components/driver/test_apps/legacy_rmt_driver/components/infrared_tools/src/ir_parser_rmt_rc5.c index ede3b4290b..fa5eabdc8d 100644 --- a/examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_parser_rmt_rc5.c +++ b/components/driver/test_apps/legacy_rmt_driver/components/infrared_tools/src/ir_parser_rmt_rc5.c @@ -1,16 +1,8 @@ -// Copyright 2019 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. +/* + * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #include #include #include "esp_log.h" diff --git a/components/driver/test_apps/legacy_rmt_driver/main/CMakeLists.txt b/components/driver/test_apps/legacy_rmt_driver/main/CMakeLists.txt new file mode 100644 index 0000000000..f8bd38290a --- /dev/null +++ b/components/driver/test_apps/legacy_rmt_driver/main/CMakeLists.txt @@ -0,0 +1,5 @@ +set(srcs "test_app_main.c" + "test_legacy_rmt.c") + +idf_component_register(SRCS ${srcs} + WHOLE_ARCHIVE) diff --git a/components/driver/test_apps/legacy_rmt_driver/main/test_app_main.c b/components/driver/test_apps/legacy_rmt_driver/main/test_app_main.c new file mode 100644 index 0000000000..183a88ae3c --- /dev/null +++ b/components/driver/test_apps/legacy_rmt_driver/main/test_app_main.c @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +#define TEST_MEMORY_LEAK_THRESHOLD (-600) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + unity_run_menu(); +} diff --git a/components/driver/test/test_rmt.c b/components/driver/test_apps/legacy_rmt_driver/main/test_legacy_rmt.c similarity index 99% rename from components/driver/test/test_rmt.c rename to components/driver/test_apps/legacy_rmt_driver/main/test_legacy_rmt.c index aeff44a124..cb3b005ba3 100644 --- a/components/driver/test/test_rmt.c +++ b/components/driver/test_apps/legacy_rmt_driver/main/test_legacy_rmt.c @@ -3,7 +3,6 @@ * * SPDX-License-Identifier: Apache-2.0 */ -// RMT driver unit test is based on extended NEC protocol #include #include @@ -14,10 +13,8 @@ #include "freertos/task.h" #include "esp_log.h" #include "unity.h" -#include "test_utils.h" #include "esp_rom_gpio.h" -#if SOC_RMT_SUPPORTED #include "ir_tools.h" #include "driver/rmt.h" @@ -32,6 +29,7 @@ #define RMT_TESTBENCH_FLAGS_LOOP_ON (1<<2) static const char *TAG = "RMT.test"; + static ir_builder_t *s_ir_builder = NULL; static ir_parser_t *s_ir_parser = NULL; @@ -598,5 +596,3 @@ TEST_CASE("RMT TX loop", "[rmt]") rmt_clean_testbench(tx_channel, rx_channel); } #endif - -#endif // SOC_RMT_SUPPORTED diff --git a/components/driver/test_apps/legacy_rmt_driver/pytest_legacy_rmt_driver.py b/components/driver/test_apps/legacy_rmt_driver/pytest_legacy_rmt_driver.py new file mode 100644 index 0000000000..84b97179c9 --- /dev/null +++ b/components/driver/test_apps/legacy_rmt_driver/pytest_legacy_rmt_driver.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +@pytest.mark.parametrize('config', [ + 'release', +], indirect=True) +def test_legacy_rmt(dut: Dut) -> None: + dut.expect('Press ENTER to see the list of tests') + dut.write('*') + dut.expect_unity_test_output(timeout=120) diff --git a/components/driver/test_apps/legacy_rmt_driver/sdkconfig.ci.release b/components/driver/test_apps/legacy_rmt_driver/sdkconfig.ci.release new file mode 100644 index 0000000000..91d93f163e --- /dev/null +++ b/components/driver/test_apps/legacy_rmt_driver/sdkconfig.ci.release @@ -0,0 +1,5 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/driver/test_apps/legacy_rmt_driver/sdkconfig.defaults b/components/driver/test_apps/legacy_rmt_driver/sdkconfig.defaults new file mode 100644 index 0000000000..c16b1aed99 --- /dev/null +++ b/components/driver/test_apps/legacy_rmt_driver/sdkconfig.defaults @@ -0,0 +1,3 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n +CONFIG_RMT_SUPPRESS_DEPRECATE_WARN=y diff --git a/examples/peripherals/rmt/ir_protocols/components/infrared_tools/include/ir_timings.h b/examples/peripherals/rmt/ir_protocols/components/infrared_tools/include/ir_timings.h deleted file mode 100644 index 2325059c2f..0000000000 --- a/examples/peripherals/rmt/ir_protocols/components/infrared_tools/include/ir_timings.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2019 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. - -#pragma once - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Timings for NEC protocol - * - */ -#define NEC_LEADING_CODE_HIGH_US (9000) -#define NEC_LEADING_CODE_LOW_US (4500) -#define NEC_PAYLOAD_ONE_HIGH_US (560) -#define NEC_PAYLOAD_ONE_LOW_US (1690) -#define NEC_PAYLOAD_ZERO_HIGH_US (560) -#define NEC_PAYLOAD_ZERO_LOW_US (560) -#define NEC_REPEAT_CODE_HIGH_US (9000) -#define NEC_REPEAT_CODE_LOW_US (2250) -#define NEC_ENDING_CODE_HIGH_US (560) - -/** - * @brief Timings for RC5 protocol - * - */ -#define RC5_PULSE_DURATION_US (889) - -#ifdef __cplusplus -} -#endif diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index 7284511f4d..94a8e6b6b6 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -2073,11 +2073,6 @@ examples/peripherals/mcpwm/mcpwm_brushed_dc_control/main/mcpwm_brushed_dc_contro examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/mcpwm_capture_hc_sr04.c examples/peripherals/mcpwm/mcpwm_servo_control/main/mcpwm_servo_control_example_main.c examples/peripherals/mcpwm/mcpwm_sync_example/main/mcpwm_sync_example.c -examples/peripherals/rmt/ir_protocols/components/infrared_tools/include/ir_timings.h -examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_builder_rmt_nec.c -examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_builder_rmt_rc5.c -examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_parser_rmt_nec.c -examples/peripherals/rmt/ir_protocols/components/infrared_tools/src/ir_parser_rmt_rc5.c examples/peripherals/rmt/ir_protocols/example_test.py examples/peripherals/rmt/ir_protocols/main/ir_protocols_main.c examples/peripherals/rmt/led_strip/main/led_strip_main.c diff --git a/tools/unit-test-app/CMakeLists.txt b/tools/unit-test-app/CMakeLists.txt index 24351c17bc..0db4c32b4a 100644 --- a/tools/unit-test-app/CMakeLists.txt +++ b/tools/unit-test-app/CMakeLists.txt @@ -2,8 +2,7 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) -list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/" - "$ENV{IDF_PATH}/examples/peripherals/rmt/ir_protocols/components") +list(APPEND EXTRA_COMPONENT_DIRS "$ENV{IDF_PATH}/examples/cxx/experimental/experimental_cpp_component/") include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(unit-test-app) diff --git a/tools/unit-test-app/components/test_utils/ref_clock_impl_rmt_pcnt.c b/tools/unit-test-app/components/test_utils/ref_clock_impl_rmt_pcnt.c index 0c7225befc..14ce9dc992 100644 --- a/tools/unit-test-app/components/test_utils/ref_clock_impl_rmt_pcnt.c +++ b/tools/unit-test-app/components/test_utils/ref_clock_impl_rmt_pcnt.c @@ -25,8 +25,8 @@ #include "freertos/FreeRTOS.h" #include "esp_intr_alloc.h" #include "esp_private/periph_ctrl.h" -#include "driver/rmt.h" #include "driver/pulse_cnt.h" +#include "driver/rmt_types_legacy.h" #include "soc/gpio_sig_map.h" #include "soc/gpio_periph.h" #include "soc/soc_caps.h" From df474e74a13dd5d16ba4799f6a7e2976c0fcd7e0 Mon Sep 17 00:00:00 2001 From: morris Date: Sat, 7 May 2022 14:14:56 +0800 Subject: [PATCH 02/10] rmt_legacy: fix undetermined idle level Closes https://github.com/espressif/esp-idf/issues/8864 --- components/driver/deprecated/rmt_legacy.c | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/components/driver/deprecated/rmt_legacy.c b/components/driver/deprecated/rmt_legacy.c index 70ffe2e48f..6aa6a89b3f 100644 --- a/components/driver/deprecated/rmt_legacy.c +++ b/components/driver/deprecated/rmt_legacy.c @@ -769,16 +769,19 @@ static void IRAM_ATTR rmt_driver_isr_default(void *arg) } const rmt_item32_t *pdata = p_rmt->tx_data; size_t len_rem = p_rmt->tx_len_rem; + rmt_idle_level_t idle_level = rmt_ll_tx_get_idle_level(hal->regs, channel); + rmt_item32_t stop_data = (rmt_item32_t) { + .level0 = idle_level, + .duration0 = 0, + }; if (len_rem >= p_rmt->tx_sub_len) { rmt_fill_memory(channel, pdata, p_rmt->tx_sub_len, p_rmt->tx_offset); p_rmt->tx_data += p_rmt->tx_sub_len; p_rmt->tx_len_rem -= p_rmt->tx_sub_len; } else if (len_rem == 0) { - rmt_item32_t stop_data = {0}; rmt_fill_memory(channel, &stop_data, 1, p_rmt->tx_offset); } else { rmt_fill_memory(channel, pdata, len_rem, p_rmt->tx_offset); - rmt_item32_t stop_data = {0}; rmt_fill_memory(channel, &stop_data, 1, p_rmt->tx_offset + len_rem); p_rmt->tx_data += len_rem; p_rmt->tx_len_rem -= len_rem; @@ -1113,7 +1116,11 @@ esp_err_t rmt_write_items(rmt_channel_t channel, const rmt_item32_t *rmt_item, i p_rmt->tx_sub_len = item_sub_len; } else { rmt_fill_memory(channel, rmt_item, len_rem, 0); - rmt_item32_t stop_data = {0}; + rmt_idle_level_t idle_level = rmt_ll_tx_get_idle_level(rmt_contex.hal.regs, channel); + rmt_item32_t stop_data = (rmt_item32_t) { + .level0 = idle_level, + .duration0 = 0, + }; rmt_fill_memory(channel, &stop_data, 1, len_rem); p_rmt->tx_len_rem = 0; } @@ -1252,7 +1259,11 @@ esp_err_t rmt_write_sample(rmt_channel_t channel, const uint8_t *src, size_t src p_rmt->tx_sub_len = item_sub_len; p_rmt->translator = true; } else { - rmt_item32_t stop_data = {0}; + rmt_idle_level_t idle_level = rmt_ll_tx_get_idle_level(rmt_contex.hal.regs, channel); + rmt_item32_t stop_data = (rmt_item32_t) { + .level0 = idle_level, + .duration0 = 0, + }; rmt_fill_memory(channel, &stop_data, 1, p_rmt->tx_len_rem); p_rmt->tx_len_rem = 0; p_rmt->sample_cur = NULL; From 2fb43820c229f2268d3fdf7770057d601906f570 Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 7 Apr 2022 11:59:46 +0800 Subject: [PATCH 03/10] driver_ng: implement new rmt driver The legacy driver can't handle the breaking change between esp chips very well. And it's not elegant to extend new feature like DMA, ETM. The new driver can return a opaque handle for each RMT channel. An obvious transaction concept was also introduced. TX and RX functionalities are splited out. --- components/driver/CMakeLists.txt | 2 +- components/driver/Kconfig | 16 + components/driver/deprecated/rmt_legacy.c | 1 - components/driver/include/driver/rmt_common.h | 83 ++ .../driver/include/driver/rmt_encoder.h | 138 +++ components/driver/include/driver/rmt_rx.h | 103 ++ components/driver/include/driver/rmt_tx.h | 174 +++ components/driver/include/driver/rmt_types.h | 71 ++ components/driver/rmt/rmt_common.c | 186 +++ components/driver/rmt/rmt_encoder.c | 301 +++++ components/driver/rmt/rmt_private.h | 194 +++ components/driver/rmt/rmt_rx.c | 645 ++++++++++ components/driver/rmt/rmt_tx.c | 1043 +++++++++++++++++ .../driver/test_apps/rmt/CMakeLists.txt | 18 + components/driver/test_apps/rmt/README.md | 2 + .../driver/test_apps/rmt/main/CMakeLists.txt | 8 + .../driver/test_apps/rmt/main/test_app_main.c | 52 + .../test_apps/rmt/main/test_rmt_common.c | 100 ++ .../driver/test_apps/rmt/main/test_rmt_rx.c | 202 ++++ .../driver/test_apps/rmt/main/test_rmt_tx.c | 607 ++++++++++ .../rmt/main/test_util_rmt_encoders.c | 214 ++++ .../rmt/main/test_util_rmt_encoders.h | 21 + components/driver/test_apps/rmt/pytest_rmt.py | 24 + .../test_apps/rmt/sdkconfig.ci.iram_safe | 7 + .../driver/test_apps/rmt/sdkconfig.ci.release | 5 + .../driver/test_apps/rmt/sdkconfig.defaults | 2 + components/hal/include/hal/rmt_hal.h | 7 + components/hal/include/hal/rmt_types.h | 2 +- components/hal/rmt_hal.c | 15 + .../esp32c3/include/soc/Kconfig.soc_caps.in | 2 +- components/soc/esp32c3/include/soc/soc_caps.h | 2 +- .../esp32h2/include/soc/Kconfig.soc_caps.in | 2 +- components/soc/esp32h2/include/soc/soc_caps.h | 2 +- .../esp32s2/include/soc/Kconfig.soc_caps.in | 2 +- components/soc/esp32s2/include/soc/soc_caps.h | 2 +- .../esp32s3/include/soc/Kconfig.soc_caps.in | 2 +- components/soc/esp32s3/include/soc/soc_caps.h | 2 +- 37 files changed, 4248 insertions(+), 11 deletions(-) create mode 100644 components/driver/include/driver/rmt_common.h create mode 100644 components/driver/include/driver/rmt_encoder.h create mode 100644 components/driver/include/driver/rmt_rx.h create mode 100644 components/driver/include/driver/rmt_tx.h create mode 100644 components/driver/include/driver/rmt_types.h create mode 100644 components/driver/rmt/rmt_common.c create mode 100644 components/driver/rmt/rmt_encoder.c create mode 100644 components/driver/rmt/rmt_private.h create mode 100644 components/driver/rmt/rmt_rx.c create mode 100644 components/driver/rmt/rmt_tx.c create mode 100644 components/driver/test_apps/rmt/CMakeLists.txt create mode 100644 components/driver/test_apps/rmt/README.md create mode 100644 components/driver/test_apps/rmt/main/CMakeLists.txt create mode 100644 components/driver/test_apps/rmt/main/test_app_main.c create mode 100644 components/driver/test_apps/rmt/main/test_rmt_common.c create mode 100644 components/driver/test_apps/rmt/main/test_rmt_rx.c create mode 100644 components/driver/test_apps/rmt/main/test_rmt_tx.c create mode 100644 components/driver/test_apps/rmt/main/test_util_rmt_encoders.c create mode 100644 components/driver/test_apps/rmt/main/test_util_rmt_encoders.h create mode 100644 components/driver/test_apps/rmt/pytest_rmt.py create mode 100644 components/driver/test_apps/rmt/sdkconfig.ci.iram_safe create mode 100644 components/driver/test_apps/rmt/sdkconfig.ci.release create mode 100644 components/driver/test_apps/rmt/sdkconfig.defaults diff --git a/components/driver/CMakeLists.txt b/components/driver/CMakeLists.txt index e64af28e18..51ea19c312 100644 --- a/components/driver/CMakeLists.txt +++ b/components/driver/CMakeLists.txt @@ -46,7 +46,7 @@ if(CONFIG_SOC_SIGMADELTA_SUPPORTED) endif() if(CONFIG_SOC_RMT_SUPPORTED) - list(APPEND srcs "rmt.c") + list(APPEND srcs "rmt/rmt_common.c" "rmt/rmt_encoder.c" "rmt/rmt_rx.c" "rmt/rmt_tx.c" "deprecated/rmt_legacy.c") endif() if(CONFIG_SOC_PCNT_SUPPORTED) diff --git a/components/driver/Kconfig b/components/driver/Kconfig index 366e5ff831..3a33772188 100644 --- a/components/driver/Kconfig +++ b/components/driver/Kconfig @@ -281,6 +281,15 @@ menu "Driver configurations" menu "RMT Configuration" depends on SOC_RMT_SUPPORTED + config RMT_ISR_IRAM_SAFE + bool "RMT ISR IRAM-Safe" + default n + select GDMA_ISR_IRAM_SAFE if SOC_RMT_SUPPORT_DMA # RMT basic functionality relies on GDMA callback + select GDMA_CTRL_FUNC_IN_IRAM if SOC_RMT_SUPPORT_DMA # RMT needs to restart the GDMA in the interrupt + help + Ensure the RMT interrupt is IRAM-Safe by allowing the interrupt handler to be + executable when the cache is disabled (e.g. SPI Flash write). + config RMT_SUPPRESS_DEPRECATE_WARN bool "Suppress legacy driver deprecated warning" default n @@ -288,6 +297,13 @@ menu "Driver configurations" Wether to suppress the deprecation warnings when using legacy rmt driver (driver/rmt.h). If you want to continue using the legacy driver, and don't want to see related deprecation warnings, you can enable this option. + + config RMT_ENABLE_DEBUG_LOG + bool "Enable debug log" + default n + help + Wether to enable the debug log message for RMT driver. + Note that, this option only controls the RMT driver log, won't affect other drivers. endmenu # RMT Configuration endmenu # Driver configurations diff --git a/components/driver/deprecated/rmt_legacy.c b/components/driver/deprecated/rmt_legacy.c index 6aa6a89b3f..e4659eb9e9 100644 --- a/components/driver/deprecated/rmt_legacy.c +++ b/components/driver/deprecated/rmt_legacy.c @@ -14,7 +14,6 @@ #include "driver/gpio.h" #include "esp_private/periph_ctrl.h" #include "driver/rmt_types_legacy.h" -#include "driver_ng.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/semphr.h" diff --git a/components/driver/include/driver/rmt_common.h b/components/driver/include/driver/rmt_common.h new file mode 100644 index 0000000000..a2223e9027 --- /dev/null +++ b/components/driver/include/driver/rmt_common.h @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "driver/rmt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief RMT carrier wave configuration (for either modulation or demodulation) + */ +typedef struct { + uint32_t frequency_hz; /*!< Carrier wave frequency, in Hz, 0 means disabling the carrier */ + float duty_cycle; /*!< Carrier wave duty cycle (0~100%) */ + struct { + uint32_t polarity_active_low: 1; /*!< Specify the polarity of carrier, by default it's modulated to base signal's high level */ + uint32_t always_on: 1; /*!< If set, the carrier can always exist even there's not transfer undergoing */ + } flags; +} rmt_carrier_config_t; + +/** + * @brief Delete an RMT channel + * + * @param[in] channel RMT generic channel that created by `rmt_new_tx_channel()` or `rmt_new_rx_channel()` + * @return + * - ESP_OK: Delete RMT channel successfully + * - ESP_ERR_INVALID_ARG: Delete RMT channel failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Delete RMT channel failed because it is still in working + * - ESP_FAIL: Delete RMT channel failed because of other error + */ +esp_err_t rmt_del_channel(rmt_channel_handle_t channel); + +/** + * @brief Apply modulation feature for TX channel or demodulation feature for RX channel + * + * @param[in] channel RMT generic channel that created by `rmt_new_tx_channel()` or `rmt_new_rx_channel()` + * @param[in] config Carrier configuration. Specially, a NULL config means to disable the carrier modulation or demodulation feature + * @return + * - ESP_OK: Apply carrier configuration successfully + * - ESP_ERR_INVALID_ARG: Apply carrier configuration failed because of invalid argument + * - ESP_FAIL: Apply carrier configuration failed because of other error + */ +esp_err_t rmt_apply_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config); + +/** + * @brief Enable the RMT channel + * + * @note This function will acquire a PM lock that might be installed during channel allocation + * + * @param[in] channel RMT generic channel that created by `rmt_new_tx_channel()` or `rmt_new_rx_channel()` + * @return + * - ESP_OK: Enable RMT channel successfully + * - ESP_ERR_INVALID_ARG: Enable RMT channel failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Enable RMT channel failed because it's enabled already + * - ESP_FAIL: Enable RMT channel failed because of other error + */ +esp_err_t rmt_enable(rmt_channel_handle_t channel); + +/** + * @brief Disable the RMT channel + * + * @note This function will release a PM lock that might be installed during channel allocation + * + * @param[in] channel RMT generic channel that created by `rmt_new_tx_channel()` or `rmt_new_rx_channel()` + * @return + * - ESP_OK: Disable RMT channel successfully + * - ESP_ERR_INVALID_ARG: Disable RMT channel failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Disable RMT channel failed because it's not enabled yet + * - ESP_FAIL: Disable RMT channel failed because of other error + */ +esp_err_t rmt_disable(rmt_channel_handle_t channel); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/rmt_encoder.h b/components/driver/include/driver/rmt_encoder.h new file mode 100644 index 0000000000..ccf2aa130e --- /dev/null +++ b/components/driver/include/driver/rmt_encoder.h @@ -0,0 +1,138 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include "esp_err.h" +#include "hal/rmt_types.h" +#include "driver/rmt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of RMT encoder + */ +typedef struct rmt_encoder_t rmt_encoder_t; + +/** + * @brief RMT encoding state + */ +typedef enum { + RMT_ENCODING_COMPLETE = (1 << 0), /*!< The encoding session is finished, the caller can continue with subsequent encoding */ + RMT_ENCODING_MEM_FULL = (1 << 1), /*!< The encoding artifact memory is full, the caller should return from current encoding session */ +} rmt_encode_state_t; + +/** + * @brief Interface of RMT encoder + */ +struct rmt_encoder_t { + /** + * @brief Encode the user data into RMT symbols and write into RMT memory + * + * @note The encoding function will also be called from an ISR context, thus the function must not call any blocking API. + * @note It's recommended to put this function implementation in the IRAM, to achieve a high performance and less interrupt latency. + * + * @param[in] encoder Encoder handle + * @param[in] tx_channel RMT TX channel handle, returned from `rmt_new_tx_channel()` + * @param[in] primary_data App data to be encoded into RMT symbols + * @param[in] data_size Size of primary_data, in bytes + * @param[out] ret_state Returned current encoder's state + * @return Number of RMT symbols that the primary data has been encoded into + */ + size_t (*encode)(rmt_encoder_t *encoder, rmt_channel_handle_t tx_channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state); + + /** + * @brief Reset encoding state + * + * @param[in] encoder Encoder handle + * @return + * - ESP_OK: reset encoder successfully + * - ESP_FAIL: reset encoder failed + */ + esp_err_t (*reset)(rmt_encoder_t *encoder); + + /** + * @brief Delete encoder object + * + * @param[in] encoder Encoder handle + * @return + * - ESP_OK: delete encoder successfully + * - ESP_FAIL: delete encoder failed + */ + esp_err_t (*del)(rmt_encoder_t *encoder); +}; + +/** + * @brief Bytes encoder configuration + */ +typedef struct { + rmt_symbol_word_t bit0; /*!< How to represent BIT0 in RMT symbol */ + rmt_symbol_word_t bit1; /*!< How to represent BIT1 in RMT symbol */ + struct { + uint32_t msb_first: 1; /*!< Whether to encode MSB bit first */ + } flags; +} rmt_bytes_encoder_config_t; + +/** + * @brief Copy encoder configuration + */ +typedef struct { +} rmt_copy_encoder_config_t; + +/** + * @brief Create RMT bytes encoder, which can encode byte stream into RMT symbols + * + * @param[in] config Bytes encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_OK: Create RMT bytes encoder successfully + * - ESP_ERR_INVALID_ARG: Create RMT bytes encoder failed because of invalid argument + * - ESP_ERR_NO_MEM: Create RMT bytes encoder failed because out of memory + * - ESP_FAIL: Create RMT bytes encoder failed because of other error + */ +esp_err_t rmt_new_bytes_encoder(const rmt_bytes_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +/** + * @brief Create RMT copy encoder, which copies the given RMT symbols into RMT memory + * + * @param[in] config Copy encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_OK: Create RMT copy encoder successfully + * - ESP_ERR_INVALID_ARG: Create RMT copy encoder failed because of invalid argument + * - ESP_ERR_NO_MEM: Create RMT copy encoder failed because out of memory + * - ESP_FAIL: Create RMT copy encoder failed because of other error + */ +esp_err_t rmt_new_copy_encoder(const rmt_copy_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +/** + * @brief Delete RMT encoder + * + * @param[in] encoder RMT encoder handle, created by e.g `rmt_new_bytes_encoder()` + * @return + * - ESP_OK: Delete RMT encoder successfully + * - ESP_ERR_INVALID_ARG: Delete RMT encoder failed because of invalid argument + * - ESP_FAIL: Delete RMT encoder failed because of other error + */ +esp_err_t rmt_del_encoder(rmt_encoder_handle_t encoder); + +/** + * @brief Reset RMT encoder + * + * @param[in] encoder RMT encoder handle, created by e.g `rmt_new_bytes_encoder()` + * @return + * - ESP_OK: Reset RMT encoder successfully + * - ESP_ERR_INVALID_ARG: Reset RMT encoder failed because of invalid argument + * - ESP_FAIL: Reset RMT encoder failed because of other error + */ +esp_err_t rmt_encoder_reset(rmt_encoder_handle_t encoder); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/rmt_rx.h b/components/driver/include/driver/rmt_rx.h new file mode 100644 index 0000000000..4ef83e67d3 --- /dev/null +++ b/components/driver/include/driver/rmt_rx.h @@ -0,0 +1,103 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "driver/rmt_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Group of RMT RX callbacks + * @note The callbacks are all running under ISR environment + * @note When CONFIG_RMT_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. + */ +typedef struct { + rmt_rx_done_callback_t on_recv_done; /*!< Event callback, invoked when one RMT channel receiving transaction completes */ +} rmt_rx_event_callbacks_t; + +/** + * @brief RMT RX channel specific configuration + */ +typedef struct { + int gpio_num; /*!< GPIO number used by RMT RX channel. Set to -1 if unused */ + rmt_clock_source_t clk_src; /*!< Clock source of RMT RX channel, channels in the same group must use the same clock source */ + uint32_t resolution_hz; /*!< Channel clock resolution, in Hz */ + size_t mem_block_symbols; /*!< Size of memory block, in number of `rmt_symbol_word_t`, must be an even */ + struct { + uint32_t invert_in: 1; /*!< Whether to invert the incoming RMT channel signal */ + uint32_t with_dma: 1; /*!< If set, the driver will allocate an RMT channel with DMA capability */ + uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */ + } flags; +} rmt_rx_channel_config_t; + +/** + * @brief RMT receive specific configuration + */ +typedef struct { + uint32_t signal_range_min_ns; /*!< A pulse whose width is smaller than this threshold will be treated as glitch and ignored */ + uint32_t signal_range_max_ns; /*!< RMT will stop receiving if one symbol level has kept more than `signal_range_max_ns` */ +} rmt_receive_config_t; + +/** + * @brief Create a RMT RX channel + * + * @param[in] config RX channel configurations + * @param[out] ret_chan Returned generic RMT channel handle + * @return + * - ESP_OK: Create RMT RX channel successfully + * - ESP_ERR_INVALID_ARG: Create RMT RX channel failed because of invalid argument + * - ESP_ERR_NO_MEM: Create RMT RX channel failed because out of memory + * - ESP_ERR_NOT_FOUND: Create RMT RX channel failed because all RMT channels are used up and no more free one + * - ESP_ERR_NOT_SUPPORTED: Create RMT RX channel failed because some feature is not supported by hardware, e.g. DMA feature is not supported by hardware + * - ESP_FAIL: Create RMT RX channel failed because of other error + */ +esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_handle_t *ret_chan); + +/** + * @brief Initiate a receive job for RMT RX channel + * + * @note This function is non-blocking, it initiates a new receive job and then returns. + * User should check the received data from the `on_recv_done` callback that registered by `rmt_rx_register_event_callbacks()`. + * + * @param[in] rx_channel RMT RX channel that created by `rmt_new_rx_channel()` + * @param[in] buffer The buffer to store the received RMT symbols + * @param[in] buffer_size size of the `buffer`, in bytes + * @param[in] config Receive specific configurations + * @return + * - ESP_OK: Initiate receive job successfully + * - ESP_ERR_INVALID_ARG: Initiate receive job failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Initiate receive job failed because channel is not enabled + * - ESP_FAIL: Initiate receive job failed because of other error + */ +esp_err_t rmt_receive(rmt_channel_handle_t rx_channel, void *buffer, size_t buffer_size, const rmt_receive_config_t *config); + +/** + * @brief Set callbacks for RMT RX channel + * + * @note User can deregister a previously registered callback by calling this function and setting the callback member in the `cbs` structure to NULL. + * @note When CONFIG_RMT_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM. + * + * @param[in] rx_channel RMT generic channel that created by `rmt_new_rx_channel()` + * @param[in] cbs Group of callback functions + * @param[in] user_data User data, which will be passed to callback functions directly + * @return + * - ESP_OK: Set event callbacks successfully + * - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument + * - ESP_FAIL: Set event callbacks failed because of other error + */ +esp_err_t rmt_rx_register_event_callbacks(rmt_channel_handle_t rx_channel, const rmt_rx_event_callbacks_t *cbs, void *user_data); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/rmt_tx.h b/components/driver/include/driver/rmt_tx.h new file mode 100644 index 0000000000..058b9ad4ce --- /dev/null +++ b/components/driver/include/driver/rmt_tx.h @@ -0,0 +1,174 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include "esp_err.h" +#include "driver/rmt_common.h" +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Group of RMT TX callbacks + * @note The callbacks are all running under ISR environment + * @note When CONFIG_RMT_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. + */ +typedef struct { + rmt_tx_done_callback_t on_trans_done; /*!< Event callback, invoked when transmission is finished */ +} rmt_tx_event_callbacks_t; + +/** + * @brief RMT TX channel specific configuration + */ +typedef struct { + int gpio_num; /*!< GPIO number used by RMT TX channel. Set to -1 if unused */ + rmt_clock_source_t clk_src; /*!< Clock source of RMT TX channel, channels in the same group must use the same clock source */ + uint32_t resolution_hz; /*!< Channel clock resolution, in Hz */ + size_t mem_block_symbols; /*!< Size of memory block, in number of `rmt_symbol_word_t`, must be an even */ + size_t trans_queue_depth; /*!< Depth of internal transfer queue, increase this value can support more transfers pending in the background */ + struct { + uint32_t invert_out: 1; /*!< Whether to invert the RMT channel signal before output to GPIO pad */ + uint32_t with_dma: 1; /*!< If set, the driver will allocate an RMT channel with DMA capability */ + uint32_t io_loop_back: 1; /*!< For debug/test, the signal output from the GPIO will be fed to the input path as well */ + } flags; +} rmt_tx_channel_config_t; + +/** + * @brief RMT transmit specific configuration + */ +typedef struct { + int loop_count; /*!< Specify the times of transmission in a loop, -1 means transmitting in an infinite loop */ + struct { + uint32_t eot_level : 1; /*!< Set the output level for the "End Of Transmission" */ + } flags; +} rmt_transmit_config_t; + +/** + * @brief Synchronous manager configuration + */ +typedef struct { + const rmt_channel_handle_t *tx_channel_array; /*!< Array of TX channels that are about to be managed by a synchronous controller */ + size_t array_size; /*!< Size of the `tx_channel_array` */ +} rmt_sync_manager_config_t; + +/** + * @brief Create a RMT TX channel + * + * @param[in] config TX channel configurations + * @param[out] ret_chan Returned generic RMT channel handle + * @return + * - ESP_OK: Create RMT TX channel successfully + * - ESP_ERR_INVALID_ARG: Create RMT TX channel failed because of invalid argument + * - ESP_ERR_NO_MEM: Create RMT TX channel failed because out of memory + * - ESP_ERR_NOT_FOUND: Create RMT TX channel failed because all RMT channels are used up and no more free one + * - ESP_ERR_NOT_SUPPORTED: Create RMT TX channel failed because some feature is not supported by hardware, e.g. DMA feature is not supported by hardware + * - ESP_FAIL: Create RMT TX channel failed because of other error + */ +esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_handle_t *ret_chan); + +/** + * @brief Transmit data by RMT TX channel + * + * @note This function will construct a transaction descriptor and push to a queue. The transaction will not start immediately until it's dispatched in the ISR. + * If there're too many transactions pending in the queue, this function will block until the queue has free space. + * @note The data to be transmitted will be encoded into RMT symbols by the specific `encoder`. + * + * @param[in] tx_channel RMT TX channel that created by `rmt_new_tx_channel()` + * @param[in] encoder RMT encoder that created by various factory APIs like `rmt_new_bytes_encoder()` + * @param[in] payload The raw data to be encoded into RMT symbols + * @param[in] payload_bytes Size of the `payload` in bytes + * @param[in] config Transmission specific configuration + * @return + * - ESP_OK: Transmit data successfully + * - ESP_ERR_INVALID_ARG: Transmit data failed because of invalid argument + * - ESP_ERR_INVALID_STATE: Transmit data failed because channel is not enabled + * - ESP_ERR_NOT_SUPPORTED: Transmit data failed because some feature is not supported by hardware, e.g. unsupported loop count + * - ESP_FAIL: Transmit data failed because of other error + */ +esp_err_t rmt_transmit(rmt_channel_handle_t tx_channel, rmt_encoder_handle_t encoder, const void *payload, size_t payload_bytes, const rmt_transmit_config_t *config); + +/** + * @brief Wait for all pending TX transactions done + * + * @note This function will block forever if the pending transaction can't be finished within a limited time (e.g. an infinite loop transaction). + * See also `rmt_disable()` for how to terminate a working channel. + * + * @param[in] tx_channel RMT TX channel that created by `rmt_new_tx_channel()` + * @param[in] timeout_ms Wait timeout, in ms. Specially, -1 means to wait forever. + * @return + * - ESP_OK: Flush transactions successfully + * - ESP_ERR_INVALID_ARG: Flush transactions failed because of invalid argument + * - ESP_ERR_TIMEOUT: Flush transactions failed because of timeout + * - ESP_FAIL: Flush transactions failed because of other error + */ +esp_err_t rmt_tx_wait_all_done(rmt_channel_handle_t tx_channel, int timeout_ms); + +/** + * @brief Set event callbacks for RMT TX channel + * + * @note User can deregister a previously registered callback by calling this function and setting the callback member in the `cbs` structure to NULL. + * @note When CONFIG_RMT_ISR_IRAM_SAFE is enabled, the callback itself and functions called by it should be placed in IRAM. + * The variables used in the function should be in the SRAM as well. The `user_data` should also reside in SRAM. + * + * @param[in] tx_channel RMT generic channel that created by `rmt_new_tx_channel()` + * @param[in] cbs Group of callback functions + * @param[in] user_data User data, which will be passed to callback functions directly + * @return + * - ESP_OK: Set event callbacks successfully + * - ESP_ERR_INVALID_ARG: Set event callbacks failed because of invalid argument + * - ESP_FAIL: Set event callbacks failed because of other error + */ +esp_err_t rmt_tx_register_event_callbacks(rmt_channel_handle_t tx_channel, const rmt_tx_event_callbacks_t *cbs, void *user_data); + +/** + * @brief Create a synchronization manager for multiple TX channels, so that the managed channel can start transmitting at the same time + * + * @note All the channels to be managed should be enabled by `rmt_enable()` before put them into sync manager. + * + * @param[in] config Synchronization manager configuration + * @param[out] ret_synchro Returned synchronization manager handle + * @return + * - ESP_OK: Create sync manager successfully + * - ESP_ERR_INVALID_ARG: Create sync manager failed because of invalid argument + * - ESP_ERR_NOT_SUPPORTED: Create sync manager failed because it is not supported by hardware + * - ESP_ERR_INVALID_STATE: Create sync manager failed because not all channels are enabled + * - ESP_ERR_NO_MEM: Create sync manager failed because out of memory + * - ESP_ERR_NOT_FOUND: Create sync manager failed because all sync controllers are used up and no more free one + * - ESP_FAIL: Create sync manager failed because of other error + */ +esp_err_t rmt_new_sync_manager(const rmt_sync_manager_config_t *config, rmt_sync_manager_handle_t *ret_synchro); + +/** + * @brief Delete synchronization manager + * + * @param[in] synchro Synchronization manager handle returned from `rmt_new_sync_manager()` + * @return + * - ESP_OK: Delete the synchronization manager successfully + * - ESP_ERR_INVALID_ARG: Delete the synchronization manager failed because of invalid argument + * - ESP_FAIL: Delete the synchronization manager failed because of other error + */ +esp_err_t rmt_del_sync_manager(rmt_sync_manager_handle_t synchro); + +/** + * @brief Reset synchronization manager + * + * @param[in] synchro Synchronization manager handle returned from `rmt_new_sync_manager()` + * @return + * - ESP_OK: Reset the synchronization manager successfully + * - ESP_ERR_INVALID_ARG: Reset the synchronization manager failed because of invalid argument + * - ESP_FAIL: Reset the synchronization manager failed because of other error + */ +esp_err_t rmt_sync_reset(rmt_sync_manager_handle_t synchro); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/include/driver/rmt_types.h b/components/driver/include/driver/rmt_types.h new file mode 100644 index 0000000000..dd05b4cae0 --- /dev/null +++ b/components/driver/include/driver/rmt_types.h @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include +#include +#include "hal/rmt_types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of RMT channel handle + */ +typedef struct rmt_channel_t *rmt_channel_handle_t; + +/** + * @brief Type of RMT synchronization manager handle + */ +typedef struct rmt_sync_manager_t *rmt_sync_manager_handle_t; + +/** + * @brief Type of RMT encoder handle + */ +typedef struct rmt_encoder_t *rmt_encoder_handle_t; + +/** + * @brief Type of RMT TX done event data + */ +typedef struct { + size_t num_symbols; /*!< The number of transmitted RMT symbols (only one round is counted if it's a loop transmission) */ +} rmt_tx_done_event_data_t; + +/** + * @brief Prototype of RMT event callback + * @param[in] tx_chan RMT channel handle, created from `rmt_new_tx_channel()` + * @param[in] edata RMT event data + * @param[in] user_ctx User registered context, passed from `rmt_tx_register_event_callbacks()` + * + * @return Whether a high priority task has been waken up by this callback function + */ +typedef bool (*rmt_tx_done_callback_t)(rmt_channel_handle_t tx_chan, rmt_tx_done_event_data_t *edata, void *user_ctx); + +/** + * @brief Type of RMT RX done event data + */ +typedef struct { + rmt_symbol_word_t *received_symbols; /*!< Point to the received RMT symbols */ + size_t num_symbols; /*!< The number of received RMT symbols */ +} rmt_rx_done_event_data_t; + +/** + * @brief Prototype of RMT event callback + * + * @param[in] rx_chan RMT channel handle, created from `rmt_new_rx_channel()` + * @param[in] edata Point to RMT event data + * @param[in] user_ctx User registered context, passed from `rmt_rx_register_event_callbacks()` + * + * @return Whether a high priority task has been waken up by this function + */ +typedef bool (*rmt_rx_done_callback_t)(rmt_channel_handle_t rx_chan, rmt_rx_done_event_data_t *edata, void *user_ctx); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/rmt/rmt_common.c b/components/driver/rmt/rmt_common.c new file mode 100644 index 0000000000..9df192c4eb --- /dev/null +++ b/components/driver/rmt/rmt_common.c @@ -0,0 +1,186 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include "sdkconfig.h" +#if CONFIG_RMT_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" +#include "rmt_private.h" +#include "clk_ctrl_os.h" +#include "soc/rtc.h" +#include "soc/rmt_periph.h" +#include "hal/rmt_ll.h" +#include "driver/gpio.h" +#include "esp_private/esp_clk.h" +#include "esp_private/periph_ctrl.h" + +static const char *TAG = "rmt"; + +typedef struct rmt_platform_t { + _lock_t mutex; // platform level mutex lock + rmt_group_t *groups[SOC_RMT_GROUPS]; // array of RMT group instances + int group_ref_counts[SOC_RMT_GROUPS]; // reference count used to protect group install/uninstall +} rmt_platform_t; + +static rmt_platform_t s_platform; // singleton platform + +rmt_group_t *rmt_acquire_group_handle(int group_id) +{ + bool new_group = false; + rmt_group_t *group = NULL; + + // prevent install rmt group concurrently + _lock_acquire(&s_platform.mutex); + if (!s_platform.groups[group_id]) { + group = heap_caps_calloc(1, sizeof(rmt_group_t), RMT_MEM_ALLOC_CAPS); + if (group) { + new_group = true; + s_platform.groups[group_id] = group; + group->group_id = group_id; + group->spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + // initial occupy_mask: 1111...100...0 + group->occupy_mask = UINT32_MAX & ~((1 << SOC_RMT_CHANNELS_PER_GROUP) - 1); + // group clock won't be configured at this stage, it will be set when allocate the first channel + group->clk_src = RMT_CLK_SRC_NONE; + // enable APB access RMT registers + periph_module_enable(rmt_periph_signals.groups[group_id].module); + // hal layer initialize + rmt_hal_init(&group->hal); + } + } else { // group already install + group = s_platform.groups[group_id]; + } + if (group) { + // someone acquired the group handle means we have a new object that refer to this group + s_platform.group_ref_counts[group_id]++; + } + _lock_release(&s_platform.mutex); + + if (new_group) { + ESP_LOGD(TAG, "new group(%d) at %p, occupy=%x", group_id, group, group->occupy_mask); + } + return group; +} + +void rmt_release_group_handle(rmt_group_t *group) +{ + int group_id = group->group_id; + bool do_deinitialize = false; + + _lock_acquire(&s_platform.mutex); + s_platform.group_ref_counts[group_id]--; + if (s_platform.group_ref_counts[group_id] == 0) { + do_deinitialize = true; + s_platform.groups[group_id] = NULL; + // hal layer deinitialize + rmt_hal_deinit(&group->hal); + periph_module_disable(rmt_periph_signals.groups[group_id].module); + free(group); + } + _lock_release(&s_platform.mutex); + + if (do_deinitialize) { + ESP_LOGD(TAG, "del group(%d)", group_id); + } +} + +esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t clk_src) +{ + esp_err_t ret = ESP_OK; + rmt_group_t *group = chan->group; + int channel_id = chan->channel_id; + uint32_t periph_src_clk_hz = 0; + bool clock_selection_conflict = false; + // check if we need to update the group clock source, group clock source is shared by all channels + portENTER_CRITICAL(&group->spinlock); + if (group->clk_src == RMT_CLK_SRC_NONE) { + group->clk_src = clk_src; + } else { + clock_selection_conflict = (group->clk_src != clk_src); + } + portEXIT_CRITICAL(&group->spinlock); + ESP_RETURN_ON_FALSE(!clock_selection_conflict, ESP_ERR_INVALID_STATE, TAG, + "group clock conflict, already is %d but attempt to %d", group->clk_src, clk_src); + + // [clk_tree] TODO: replace the following switch table by clk_tree API + switch (clk_src) { +#if SOC_RMT_SUPPORT_APB + case RMT_CLK_SRC_APB: + periph_src_clk_hz = esp_clk_apb_freq(); +#if CONFIG_PM_ENABLE + sprintf(chan->pm_lock_name, "rmt_%d_%d", group->group_id, channel_id); // e.g. rmt_0_0 + ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, chan->pm_lock_name, &chan->pm_lock); + ESP_RETURN_ON_ERROR(ret, TAG, "create APB_FREQ_MAX lock failed"); + ESP_LOGD(TAG, "install APB_FREQ_MAX lock for RMT channel (%d,%d)", group->group_id, channel_id); +#endif // CONFIG_PM_ENABLE +#endif // SOC_RMT_SUPPORT_APB + break; +#if SOC_RMT_SUPPORT_AHB + case RMT_CLK_SRC_AHB: + // TODO: decide which kind of PM lock we should use for such clock + periph_src_clk_hz = 48 * 1000 * 1000; + break; +#endif // SOC_RMT_SUPPORT_AHB +#if SOC_RMT_SUPPORT_XTAL + case RMT_CLK_SRC_XTAL: + periph_src_clk_hz = esp_clk_xtal_freq(); + break; +#endif // SOC_RMT_SUPPORT_XTAL +#if SOC_RMT_SUPPORT_REF_TICK + case RMT_CLK_SRC_APB_F1M: + periph_src_clk_hz = REF_CLK_FREQ; + break; +#endif // SOC_RMT_SUPPORT_REF_TICK +#if SOC_RMT_SUPPORT_RC_FAST + case RMT_CLK_SRC_RC_FAST: + periph_rtc_dig_clk8m_enable(); + periph_src_clk_hz = periph_rtc_dig_clk8m_get_freq(); + break; +#endif // SOC_RMT_SUPPORT_RC_FAST + default: + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "clock source %d is not supported", clk_src); + break; + } + // no division for group clock source, to achieve highest resolution + rmt_ll_set_group_clock_src(group->hal.regs, channel_id, clk_src, 1, 1, 0); + group->resolution_hz = periph_src_clk_hz; + ESP_LOGD(TAG, "group clock resolution:%u", group->resolution_hz); + return ret; +} + +esp_err_t rmt_apply_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config) +{ + // specially, we allow config to be NULL, means to disable the carrier submodule + ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return channel->set_carrier_action(channel, config); +} + +esp_err_t rmt_del_channel(rmt_channel_handle_t channel) +{ + ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->fsm == RMT_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel not in init state"); + gpio_reset_pin(channel->gpio_num); + return channel->del(channel); +} + +esp_err_t rmt_enable(rmt_channel_handle_t channel) +{ + ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->fsm == RMT_FSM_INIT, ESP_ERR_INVALID_STATE, TAG, "channel not in init state"); + return channel->enable(channel); +} + +esp_err_t rmt_disable(rmt_channel_handle_t channel) +{ + ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->fsm == RMT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not in enable state"); + return channel->disable(channel); +} diff --git a/components/driver/rmt/rmt_encoder.c b/components/driver/rmt/rmt_encoder.c new file mode 100644 index 0000000000..25d8b0e7ef --- /dev/null +++ b/components/driver/rmt/rmt_encoder.c @@ -0,0 +1,301 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "sdkconfig.h" +#if CONFIG_RMT_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" +#include "driver/rmt_encoder.h" +#include "rmt_private.h" + +static const char *TAG = "rmt"; + +typedef struct rmt_bytes_encoder_t { + rmt_encoder_t base; // encoder base class + size_t last_bit_index; // index of the encoding bit position in the encoding byte + size_t last_byte_index; // index of the encoding byte in the primary stream + rmt_symbol_word_t bit0; // bit zero representing + rmt_symbol_word_t bit1; // bit one representing + struct { + uint32_t msb_first: 1; // encode MSB firstly + } flags; +} rmt_bytes_encoder_t; + +typedef struct rmt_copy_encoder_t { + rmt_encoder_t base; // encoder base class + size_t last_symbol_index; // index of symbol position in the primary stream +} rmt_copy_encoder_t; + +static esp_err_t rmt_bytes_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_bytes_encoder_t *bytes_encoder = __containerof(encoder, rmt_bytes_encoder_t, base); + // reset index to zero + bytes_encoder->last_bit_index = 0; + bytes_encoder->last_byte_index = 0; + return ESP_OK; +} + +static inline uint8_t _bitwise_reverse(uint8_t n) +{ + n = ((n & 0xf0) >> 4) | ((n & 0x0f) << 4); + n = ((n & 0xcc) >> 2) | ((n & 0x33) << 2); + n = ((n & 0xaa) >> 1) | ((n & 0x55) << 1); + return n; +} + +static size_t IRAM_ATTR rmt_encode_bytes(rmt_encoder_t *encoder, rmt_channel_handle_t channel, + const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_bytes_encoder_t *bytes_encoder = __containerof(encoder, rmt_bytes_encoder_t, base); + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + const uint8_t *nd = (const uint8_t *)primary_data; + rmt_encode_state_t state = 0; + dma_descriptor_t *desc0 = NULL; + dma_descriptor_t *desc1 = NULL; + + size_t byte_index = bytes_encoder->last_byte_index; + size_t bit_index = bytes_encoder->last_bit_index; + // how many symbols will be generated by the encoder + size_t mem_want = (data_size - byte_index - 1) * 8 + (8 - bit_index); + // how many symbols we can save for this round + size_t mem_have = tx_chan->mem_end - tx_chan->mem_off; + // where to put the encoded symbols? DMA buffer or RMT HW memory + rmt_symbol_word_t *mem_to = channel->dma_chan ? channel->dma_mem_base : channel->hw_mem_base; + // how many symbols will be encoded in this round + size_t encode_len = MIN(mem_want, mem_have); + bool encoding_truncated = mem_have < mem_want; + bool encoding_space_free = mem_have > mem_want; + + if (channel->dma_chan) { + // mark the start descriptor + if (tx_chan->mem_off < tx_chan->ping_pong_symbols) { + desc0 = &tx_chan->dma_nodes[0]; + } else { + desc0 = &tx_chan->dma_nodes[1]; + } + } + + size_t len = encode_len; + while (len > 0) { + // start from last time truncated encoding + uint8_t cur_byte = nd[byte_index]; + // bit-wise reverse + if (bytes_encoder->flags.msb_first) { + cur_byte = _bitwise_reverse(cur_byte); + } + while ((len > 0) && (bit_index < 8)) { + if (cur_byte & (1 << bit_index)) { + mem_to[tx_chan->mem_off++] = bytes_encoder->bit1; + } else { + mem_to[tx_chan->mem_off++] = bytes_encoder->bit0; + } + len--; + bit_index++; + } + if (bit_index >= 8) { + byte_index++; + bit_index = 0; + } + } + + if (channel->dma_chan) { + // mark the end descriptor + if (tx_chan->mem_off < tx_chan->ping_pong_symbols) { + desc1 = &tx_chan->dma_nodes[0]; + } else { + desc1 = &tx_chan->dma_nodes[1]; + } + + // cross line, means desc0 has prepared with sufficient data buffer + if (desc0 != desc1) { + desc0->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t); + desc0->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + } + } + + if (encoding_truncated) { + // this encoding has not finished yet, save the truncated position + bytes_encoder->last_bit_index = bit_index; + bytes_encoder->last_byte_index = byte_index; + } else { + // reset internal index if encoding session has finished + bytes_encoder->last_bit_index = 0; + bytes_encoder->last_byte_index = 0; + state |= RMT_ENCODING_COMPLETE; + } + + if (!encoding_space_free) { + // no more free memory, the caller should yield + state |= RMT_ENCODING_MEM_FULL; + } + + // reset offset pointer when exceeds maximum range + if (tx_chan->mem_off >= tx_chan->ping_pong_symbols * 2) { + if (channel->dma_chan) { + desc1->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t); + desc1->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + } + tx_chan->mem_off = 0; + } + + *ret_state = state; + return encode_len; +} + +static esp_err_t rmt_copy_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_copy_encoder_t *copy_encoder = __containerof(encoder, rmt_copy_encoder_t, base); + copy_encoder->last_symbol_index = 0; + return ESP_OK; +} + +static size_t IRAM_ATTR rmt_encode_copy(rmt_encoder_t *encoder, rmt_channel_handle_t channel, + const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_copy_encoder_t *copy_encoder = __containerof(encoder, rmt_copy_encoder_t, base); + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_symbol_word_t *symbols = (rmt_symbol_word_t *)primary_data; + rmt_encode_state_t state = 0; + dma_descriptor_t *desc0 = NULL; + dma_descriptor_t *desc1 = NULL; + + size_t symbol_index = copy_encoder->last_symbol_index; + // how many symbols will be copied by the encoder + size_t mem_want = (data_size / 4 - symbol_index); + // how many symbols we can save for this round + size_t mem_have = tx_chan->mem_end - tx_chan->mem_off; + // where to put the encoded symbols? DMA buffer or RMT HW memory + rmt_symbol_word_t *mem_to = channel->dma_chan ? channel->dma_mem_base : channel->hw_mem_base; + // how many symbols will be encoded in this round + size_t encode_len = MIN(mem_want, mem_have); + bool encoding_truncated = mem_have < mem_want; + bool encoding_space_free = mem_have > mem_want; + + if (channel->dma_chan) { + // mark the start descriptor + if (tx_chan->mem_off < tx_chan->ping_pong_symbols) { + desc0 = &tx_chan->dma_nodes[0]; + } else { + desc0 = &tx_chan->dma_nodes[1]; + } + } + + size_t len = encode_len; + while (len > 0) { + mem_to[tx_chan->mem_off++] = symbols[symbol_index++]; + len--; + } + + if (channel->dma_chan) { + // mark the end descriptor + if (tx_chan->mem_off < tx_chan->ping_pong_symbols) { + desc1 = &tx_chan->dma_nodes[0]; + } else { + desc1 = &tx_chan->dma_nodes[1]; + } + + // cross line, means desc0 has prepared with sufficient data buffer + if (desc0 != desc1) { + desc0->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t); + desc0->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + } + } + + if (encoding_truncated) { + // this encoding has not finished yet, save the truncated position + copy_encoder->last_symbol_index = symbol_index; + } else { + // reset internal index if encoding session has finished + copy_encoder->last_symbol_index = 0; + state |= RMT_ENCODING_COMPLETE; + } + + if (!encoding_space_free) { + // no more free memory, the caller should yield + state |= RMT_ENCODING_MEM_FULL; + } + + // reset offset pointer when exceeds maximum range + if (tx_chan->mem_off >= tx_chan->ping_pong_symbols * 2) { + if (channel->dma_chan) { + desc1->dw0.length = tx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t); + desc1->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + } + tx_chan->mem_off = 0; + } + + *ret_state = state; + return encode_len; +} + +static esp_err_t rmt_del_bytes_encoder(rmt_encoder_t *encoder) +{ + rmt_bytes_encoder_t *bytes_encoder = __containerof(encoder, rmt_bytes_encoder_t, base); + free(bytes_encoder); + return ESP_OK; +} + +static esp_err_t rmt_del_copy_encoder(rmt_encoder_t *encoder) +{ + rmt_copy_encoder_t *copy_encoder = __containerof(encoder, rmt_copy_encoder_t, base); + free(copy_encoder); + return ESP_OK; +} + +esp_err_t rmt_new_bytes_encoder(const rmt_bytes_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + rmt_bytes_encoder_t *encoder = heap_caps_calloc(1, sizeof(rmt_bytes_encoder_t), RMT_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for bytes encoder"); + encoder->base.encode = rmt_encode_bytes; + encoder->base.del = rmt_del_bytes_encoder; + encoder->base.reset = rmt_bytes_encoder_reset; + encoder->bit0 = config->bit0; + encoder->bit1 = config->bit1; + encoder->flags.msb_first = config->flags.msb_first; + // return general encoder handle + *ret_encoder = &encoder->base; + ESP_LOGD(TAG, "new bytes encoder @%p", encoder); +err: + return ret; +} + +esp_err_t rmt_new_copy_encoder(const rmt_copy_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + rmt_copy_encoder_t *encoder = heap_caps_calloc(1, sizeof(rmt_copy_encoder_t), RMT_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for copy encoder"); + encoder->base.encode = rmt_encode_copy; + encoder->base.del = rmt_del_copy_encoder; + encoder->base.reset = rmt_copy_encoder_reset; + // return general encoder handle + *ret_encoder = &encoder->base; + ESP_LOGD(TAG, "new copy encoder @%p", encoder); +err: + return ret; +} + +esp_err_t rmt_del_encoder(rmt_encoder_handle_t encoder) +{ + ESP_RETURN_ON_FALSE(encoder, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return encoder->del(encoder); +} + +esp_err_t rmt_encoder_reset(rmt_encoder_handle_t encoder) +{ + ESP_RETURN_ON_FALSE(encoder, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return encoder->reset(encoder); +} diff --git a/components/driver/rmt/rmt_private.h b/components/driver/rmt/rmt_private.h new file mode 100644 index 0000000000..fb2fb0f1e3 --- /dev/null +++ b/components/driver/rmt/rmt_private.h @@ -0,0 +1,194 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "freertos/queue.h" +#include "esp_err.h" +#include "soc/soc_caps.h" +#include "hal/rmt_types.h" +#include "hal/rmt_hal.h" +#include "hal/dma_types.h" +#include "esp_intr_alloc.h" +#include "esp_heap_caps.h" +#include "esp_pm.h" +#include "esp_attr.h" +#include "esp_private/gdma.h" +#include "driver/rmt_common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#if CONFIG_RMT_ISR_IRAM_SAFE +#define RMT_MEM_ALLOC_CAPS (MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT) +#else +#define RMT_MEM_ALLOC_CAPS MALLOC_CAP_DEFAULT +#endif + +// RMT driver object is per-channel, the interrupt source is shared between channels +#if CONFIG_RMT_ISR_IRAM_SAFE +#define RMT_INTR_ALLOC_FLAG (ESP_INTR_FLAG_SHARED | ESP_INTR_FLAG_IRAM) +#else +#define RMT_INTR_ALLOC_FLAG ESP_INTR_FLAG_SHARED +#endif + +// Hopefully the channel offset won't change in other targets +#define RMT_TX_CHANNEL_OFFSET_IN_GROUP 0 +#define RMT_RX_CHANNEL_OFFSET_IN_GROUP (SOC_RMT_CHANNELS_PER_GROUP - SOC_RMT_TX_CANDIDATES_PER_GROUP) + +// DMA buffer size must align to `rmt_symbol_word_t` +#define RMT_DMA_DESC_BUF_MAX_SIZE (DMA_DESCRIPTOR_BUFFER_MAX_SIZE & ~(sizeof(rmt_symbol_word_t) - 1)) + +#define RMT_DMA_NODES_PING_PONG 2 // two nodes ping-pong +#define RMT_PM_LOCK_NAME_LEN_MAX 16 + +// RMTMEM address is declared in .peripherals.ld +extern rmt_symbol_word_t RMTMEM; + +typedef enum { + RMT_CHANNEL_DIRECTION_TX, + RMT_CHANNEL_DIRECTION_RX, +} rmt_channel_direction_t; + +typedef enum { + RMT_FSM_INIT, + RMT_FSM_ENABLE, +} rmt_fsm_t; + +enum { + RMT_TX_QUEUE_READY, + RMT_TX_QUEUE_PROGRESS, + RMT_TX_QUEUE_COMPLETE, + RMT_TX_QUEUE_MAX, +}; + +typedef struct rmt_group_t rmt_group_t; +typedef struct rmt_channel_t rmt_channel_t; +typedef struct rmt_tx_channel_t rmt_tx_channel_t; +typedef struct rmt_rx_channel_t rmt_rx_channel_t; +typedef struct rmt_sync_manager_t rmt_sync_manager_t; + +struct rmt_group_t { + int group_id; // group ID, index from 0 + portMUX_TYPE spinlock; // to protect per-group register level concurrent access + rmt_hal_context_t hal; // hal layer for each group + rmt_clock_source_t clk_src; // record the group clock source, group clock is shared by all channels + uint32_t resolution_hz; // resolution of group clock + uint32_t occupy_mask; // a set bit in the mask indicates the channel is not available + rmt_tx_channel_t *tx_channels[SOC_RMT_TX_CANDIDATES_PER_GROUP]; // array of RMT TX channels + rmt_rx_channel_t *rx_channels[SOC_RMT_RX_CANDIDATES_PER_GROUP]; // array of RMT RX channels + rmt_sync_manager_t *sync_manager; // sync manager, this can be extended into an array if there're more sync controllers in one RMT group +}; + +struct rmt_channel_t { + int channel_id; // channel ID, index from 0 + int gpio_num; // GPIO number used by RMT RX channel + uint32_t channel_mask; // mask of the memory blocks that occupied by the channel + size_t mem_block_num; // number of occupied RMT memory blocks + rmt_group_t *group; // which group the channel belongs to + portMUX_TYPE spinlock; // prevent channel resource accessing by user and interrupt concurrently + uint32_t resolution_hz; // channel clock resolution + intr_handle_t intr; // allocated interrupt handle for each channel + rmt_fsm_t fsm; // channel life cycle specific FSM + rmt_channel_direction_t direction; // channel direction + rmt_symbol_word_t *hw_mem_base; // base address of RMT channel hardware memory + rmt_symbol_word_t *dma_mem_base; // base address of RMT channel DMA buffer + gdma_channel_handle_t dma_chan; // DMA channel + esp_pm_lock_handle_t pm_lock; // power management lock +#if CONFIG_PM_ENABLE + char pm_lock_name[RMT_PM_LOCK_NAME_LEN_MAX]; // pm lock name +#endif + // RMT channel common interface + // The following IO functions will have per-implementation for TX and RX channel + esp_err_t (*del)(rmt_channel_t *channel); + esp_err_t (*set_carrier_action)(rmt_channel_t *channel, const rmt_carrier_config_t *config); + esp_err_t (*enable)(rmt_channel_t *channel); + esp_err_t (*disable)(rmt_channel_t *channel); +}; + +typedef struct { + rmt_encoder_handle_t encoder; // encode user payload into RMT symbols + const void *payload; // encoder payload + size_t payload_bytes; // payload size + int loop_count; // transaction can be continued in a loop for specific times + int remain_loop_count; // user required loop count may exceed hardware limitation, the driver will transfer them in batches + size_t transmitted_symbol_num; // track the number of transmitted symbols + struct { + uint32_t eot_level : 1; // Set the output level for the "End Of Transmission" + uint32_t encoding_done: 1; // Indicate whether the encoding has finished (not the encoding of transmission) + } flags; +} rmt_tx_trans_desc_t; + +struct rmt_tx_channel_t { + rmt_channel_t base; // channel base class + size_t mem_off; // runtime argument, indicating the next writing position in the RMT hardware memory + size_t mem_end; // runtime argument, incidating the end of current writing region + size_t ping_pong_symbols; // ping-pong size (half of the RMT channel memory) + size_t queue_size; // size of transaction queue + size_t num_trans_inflight; // indicates the number of transactions that are undergoing but not recycled to ready_queue + void *queues_storage; // storage of transaction queues + QueueHandle_t trans_queues[RMT_TX_QUEUE_MAX]; // transaction queues + StaticQueue_t trans_queue_structs[RMT_TX_QUEUE_MAX]; // memory to store the static structure for trans_queues + rmt_tx_trans_desc_t *cur_trans; // points to current transaction + void *user_data; // user context + rmt_tx_done_callback_t on_trans_done; // callback, invoked on trans done + dma_descriptor_t dma_nodes[RMT_DMA_NODES_PING_PONG]; // DMA descriptor nodes, make up a circular link list + rmt_tx_trans_desc_t trans_desc_pool[]; // tranfer descriptor pool +}; + +typedef struct { + void *buffer; // buffer for saving the received symbols + size_t buffer_size; // size of the buffer, in bytes + size_t received_symbol_num; // track the number of received symbols + size_t copy_dest_off; // tracking offset in the copy destination +} rmt_rx_trans_desc_t; + +struct rmt_rx_channel_t { + rmt_channel_t base; // channel base class + size_t mem_off; // starting offset to fetch the symbols in RMTMEM + size_t ping_pong_symbols; // ping-pong size (half of the RMT channel memory) + rmt_rx_done_callback_t on_recv_done; // callback, invoked on receive done + void *user_data; // user context + rmt_rx_trans_desc_t trans_desc; // transaction description + size_t num_dma_nodes; // number of DMA nodes, determined by how big the memory block that user configures + dma_descriptor_t dma_nodes[]; // DMA link nodes +}; + +/** + * @brief Acquire RMT group handle + * + * @param group_id Group ID + * @return RMT group handle + */ +rmt_group_t *rmt_acquire_group_handle(int group_id); + +/** + * @brief Release RMT group handle + * + * @param group RMT group handle, returned from `rmt_acquire_group_handle` + */ +void rmt_release_group_handle(rmt_group_t *group); + +/** + * @brief Set clock source for RMT peripheral + * + * @param chan RMT channel handle + * @param clk_src Clock source + * @return + * - ESP_OK: Set clock source successfully + * - ESP_ERR_NOT_SUPPORTED: Set clock source failed because the clk_src is not supported + * - ESP_ERR_INVALID_STATE: Set clock source failed because the clk_src is different from other RMT channel + * - ESP_FAIL: Set clock source failed because of other error + */ +esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t clk_src); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/rmt/rmt_rx.c b/components/driver/rmt/rmt_rx.c new file mode 100644 index 0000000000..a4fc401cf4 --- /dev/null +++ b/components/driver/rmt/rmt_rx.c @@ -0,0 +1,645 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "sdkconfig.h" +#if CONFIG_RMT_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" +#include "esp_rom_gpio.h" +#include "soc/rmt_periph.h" +#include "soc/rtc.h" +#include "hal/rmt_ll.h" +#include "hal/gpio_hal.h" +#include "driver/gpio.h" +#include "driver/rmt_rx.h" +#include "rmt_private.h" + +#define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1)) + +static const char *TAG = "rmt"; + +static esp_err_t rmt_del_rx_channel(rmt_channel_handle_t channel); +static esp_err_t rmt_rx_demodulate_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config); +static esp_err_t rmt_rx_enable(rmt_channel_handle_t channel); +static esp_err_t rmt_rx_disable(rmt_channel_handle_t channel); +static void rmt_rx_default_isr(void *args); + +#if SOC_RMT_SUPPORT_DMA +static bool rmt_dma_rx_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data); + +static void rmt_rx_mount_dma_buffer(dma_descriptor_t *desc_array, size_t array_size, const void *buffer, size_t buffer_size) +{ + size_t prepared_length = 0; + uint8_t *data = (uint8_t *)buffer; + int dma_node_i = 0; + dma_descriptor_t *desc = NULL; + while (buffer_size > RMT_DMA_DESC_BUF_MAX_SIZE) { + desc = &desc_array[dma_node_i]; + desc->dw0.suc_eof = 0; + desc->dw0.size = RMT_DMA_DESC_BUF_MAX_SIZE; + desc->dw0.length = 0; + desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + desc->buffer = &data[prepared_length]; + desc->next = &desc_array[dma_node_i + 1]; + prepared_length += RMT_DMA_DESC_BUF_MAX_SIZE; + buffer_size -= RMT_DMA_DESC_BUF_MAX_SIZE; + dma_node_i++; + } + if (buffer_size) { + desc = &desc_array[dma_node_i]; + desc->dw0.suc_eof = 0; + desc->dw0.size = buffer_size; + desc->dw0.length = 0; + desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + desc->buffer = &data[prepared_length]; + prepared_length += buffer_size; + } + desc->next = NULL; // one-off DMA chain +} + +static esp_err_t rmt_rx_init_dma_link(rmt_rx_channel_t *rx_channel, const rmt_rx_channel_config_t *config) +{ + gdma_channel_alloc_config_t dma_chan_config = { + .direction = GDMA_CHANNEL_DIRECTION_RX, + }; + ESP_RETURN_ON_ERROR(gdma_new_channel(&dma_chan_config, &rx_channel->base.dma_chan), TAG, "allocate RX DMA channel failed"); + gdma_strategy_config_t gdma_strategy_conf = { + .auto_update_desc = true, + .owner_check = true, + }; + gdma_apply_strategy(rx_channel->base.dma_chan, &gdma_strategy_conf); + gdma_rx_event_callbacks_t cbs = { + .on_recv_eof = rmt_dma_rx_eof_cb, + }; + gdma_register_rx_event_callbacks(rx_channel->base.dma_chan, &cbs, rx_channel); + return ESP_OK; +} +#endif // SOC_RMT_SUPPORT_DMA + +static esp_err_t rmt_rx_register_to_group(rmt_rx_channel_t *rx_channel, const rmt_rx_channel_config_t *config) +{ + size_t mem_block_num = 0; + // start to search for a free channel + // a channel can take up its neighbour's memory block, so the neighbour channel won't work, we should skip these "invaded" ones + int channel_scan_start = RMT_RX_CHANNEL_OFFSET_IN_GROUP; + int channel_scan_end = RMT_RX_CHANNEL_OFFSET_IN_GROUP + SOC_RMT_RX_CANDIDATES_PER_GROUP; + if (config->flags.with_dma) { + // for DMA mode, the memory block number is always 1; for non-DMA mode, memory block number is configured by user + mem_block_num = 1; + // Only the last channel has the DMA capability + channel_scan_start = RMT_RX_CHANNEL_OFFSET_IN_GROUP + SOC_RMT_RX_CANDIDATES_PER_GROUP - 1; + rx_channel->ping_pong_symbols = 0; // with DMA, we don't need to do ping-pong + } else { + // one channel can occupy multiple memory blocks + mem_block_num = config->mem_block_symbols / SOC_RMT_MEM_WORDS_PER_CHANNEL; + if (mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL < config->mem_block_symbols) { + mem_block_num++; + } + rx_channel->ping_pong_symbols = mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL / 2; + } + rx_channel->base.mem_block_num = mem_block_num; + + // search free channel and then register to the group + // memory blocks used by one channel must be continuous + uint32_t channel_mask = (1 << mem_block_num) - 1; + rmt_group_t *group = NULL; + int channel_id = -1; + for (int i = 0; i < SOC_RMT_GROUPS; i++) { + group = rmt_acquire_group_handle(i); + ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i); + portENTER_CRITICAL(&group->spinlock); + for (int j = channel_scan_start; j < channel_scan_end; j++) { + if (!(group->occupy_mask & (channel_mask << j))) { + group->occupy_mask |= (channel_mask << j); + // the channel ID should index from 0 + channel_id = j - RMT_RX_CHANNEL_OFFSET_IN_GROUP; + group->rx_channels[channel_id] = rx_channel; + break; + } + } + portEXIT_CRITICAL(&group->spinlock); + if (channel_id < 0) { + // didn't find a capable channel in the group, don't forget to release the group handle + rmt_release_group_handle(group); + group = NULL; + } else { + rx_channel->base.channel_id = channel_id; + rx_channel->base.channel_mask = channel_mask; + rx_channel->base.group = group; + break; + } + } + ESP_RETURN_ON_FALSE(channel_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free rx channels"); + return ESP_OK; +} + +static void rmt_rx_unregister_from_group(rmt_channel_t *channel, rmt_group_t *group) +{ + portENTER_CRITICAL(&group->spinlock); + group->rx_channels[channel->channel_id] = NULL; + group->occupy_mask &= ~(channel->channel_mask << (channel->channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP)); + portEXIT_CRITICAL(&group->spinlock); + // channel has a reference on group, release it now + rmt_release_group_handle(group); +} + +static esp_err_t rmt_rx_destory(rmt_rx_channel_t *rx_channel) +{ + if (rx_channel->base.intr) { + ESP_RETURN_ON_ERROR(esp_intr_free(rx_channel->base.intr), TAG, "delete interrupt service failed"); + } + if (rx_channel->base.pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_delete(rx_channel->base.pm_lock), TAG, "delete pm_lock failed"); + } +#if SOC_RMT_SUPPORT_DMA + if (rx_channel->base.dma_chan) { + ESP_RETURN_ON_ERROR(gdma_del_channel(rx_channel->base.dma_chan), TAG, "delete dma channel failed"); + } +#endif // SOC_RMT_SUPPORT_DMA + if (rx_channel->base.group) { + // de-register channel from RMT group + rmt_rx_unregister_from_group(&rx_channel->base, rx_channel->base.group); + } + free(rx_channel); + return ESP_OK; +} + +esp_err_t rmt_new_rx_channel(const rmt_rx_channel_config_t *config, rmt_channel_handle_t *ret_chan) +{ +#if CONFIG_RMT_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + esp_err_t ret = ESP_OK; + rmt_rx_channel_t *rx_channel = NULL; + ESP_GOTO_ON_FALSE(config && ret_chan && config->resolution_hz, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(GPIO_IS_VALID_GPIO(config->gpio_num), ESP_ERR_INVALID_ARG, err, TAG, "invalid GPIO number"); + ESP_GOTO_ON_FALSE((config->mem_block_symbols & 0x01) == 0 && config->mem_block_symbols >= SOC_RMT_MEM_WORDS_PER_CHANNEL, + ESP_ERR_INVALID_ARG, err, TAG, "mem_block_symbols must be even and at least %d", SOC_RMT_MEM_WORDS_PER_CHANNEL); +#if !SOC_RMT_SUPPORT_DMA + ESP_GOTO_ON_FALSE(config->flags.with_dma == 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "DMA not supported"); +#endif // SOC_RMT_SUPPORT_DMA + + size_t num_dma_nodes = 0; + if (config->flags.with_dma) { + num_dma_nodes = config->mem_block_symbols * sizeof(rmt_symbol_word_t) / RMT_DMA_DESC_BUF_MAX_SIZE + 1; + } + // malloc channel memory + uint32_t mem_caps = RMT_MEM_ALLOC_CAPS; + if (config->flags.with_dma) { + // DMA descriptors must be placed in internal SRAM + mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA; + } + rx_channel = heap_caps_calloc(1, sizeof(rmt_rx_channel_t) + num_dma_nodes * sizeof(dma_descriptor_t), mem_caps); + ESP_GOTO_ON_FALSE(rx_channel, ESP_ERR_NO_MEM, err, TAG, "no mem for rx channel"); + rx_channel->num_dma_nodes = num_dma_nodes; + // register the channel to group + ESP_GOTO_ON_ERROR(rmt_rx_register_to_group(rx_channel, config), err, TAG, "register channel failed"); + rmt_group_t *group = rx_channel->base.group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = rx_channel->base.channel_id; + int group_id = group->group_id; + // select the clock source + ESP_GOTO_ON_ERROR(rmt_select_periph_clock(&rx_channel->base, config->clk_src), err, TAG, "set group clock failed"); + + // reset channel, make sure the RX engine is not working, and events are cleared + portENTER_CRITICAL(&group->spinlock); + rmt_hal_rx_channel_reset(&group->hal, channel_id); + portEXIT_CRITICAL(&group->spinlock); + + // When channel receives an end-maker, a DMA in_suc_eof interrupt will be generated + // So we don't rely on RMT interrupt any more, GDMA event callback is sufficient + if (config->flags.with_dma) { +#if SOC_RMT_SUPPORT_DMA + ESP_GOTO_ON_ERROR(rmt_rx_init_dma_link(rx_channel, config), err, TAG, "install rx DMA failed"); +#endif // SOC_RMT_SUPPORT_DMA + } else { + // RMT interrupt is mandatory if the channel doesn't use DMA + int isr_flags = RMT_INTR_ALLOC_FLAG; + ret = esp_intr_alloc_intrstatus(rmt_periph_signals.groups[group_id].irq, isr_flags, + (uint32_t)rmt_ll_get_interrupt_status_reg(hal->regs), + RMT_LL_EVENT_RX_MASK(channel_id), rmt_rx_default_isr, rx_channel, &rx_channel->base.intr); + ESP_GOTO_ON_ERROR(ret, err, TAG, "install rx interrupt failed"); + } + + // set channel clock resolution + uint32_t real_div = group->resolution_hz / config->resolution_hz; + rmt_ll_rx_set_channel_clock_div(hal->regs, channel_id, real_div); + // resolution loss due to division, calculate the real resolution + rx_channel->base.resolution_hz = group->resolution_hz / real_div; + if (rx_channel->base.resolution_hz != config->resolution_hz) { + ESP_LOGW(TAG, "channel resolution loss, real=%u", rx_channel->base.resolution_hz); + } + + rmt_ll_rx_set_mem_blocks(hal->regs, channel_id, rx_channel->base.mem_block_num); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW); +#if SOC_RMT_SUPPORT_RX_PINGPONG + rmt_ll_rx_set_limit(hal->regs, channel_id, rx_channel->ping_pong_symbols); + // always enable rx wrap, both DMA mode and ping-pong mode rely this feature + rmt_ll_rx_enable_wrap(hal->regs, channel_id, true); +#endif +#if SOC_RMT_SUPPORT_RX_DEMODULATION + // disable carrier demodulation by default, can reenable by `rmt_apply_carrier()` + rmt_ll_rx_enable_carrier_demodulation(hal->regs, channel_id, false); +#endif + + // GPIO Matrix/MUX configuration + rx_channel->base.gpio_num = config->gpio_num; + gpio_config_t gpio_conf = { + .intr_type = GPIO_INTR_DISABLE, + // also enable the input path is `io_loop_back` is on, this is useful for debug + .mode = GPIO_MODE_INPUT | (config->flags.io_loop_back ? GPIO_MODE_OUTPUT : 0), + .pull_down_en = false, + .pull_up_en = true, + .pin_bit_mask = 1ULL << config->gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config GPIO failed"); + esp_rom_gpio_connect_in_signal(config->gpio_num, + rmt_periph_signals.groups[group_id].channels[channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP].rx_sig, + config->flags.invert_in); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->gpio_num], PIN_FUNC_GPIO); + + // initialize other members of rx channel + rx_channel->base.direction = RMT_CHANNEL_DIRECTION_RX; + rx_channel->base.fsm = RMT_FSM_INIT; + rx_channel->base.hw_mem_base = &RMTMEM + (channel_id + RMT_RX_CHANNEL_OFFSET_IN_GROUP) * SOC_RMT_MEM_WORDS_PER_CHANNEL; + rx_channel->base.spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + // polymorphic methods + rx_channel->base.del = rmt_del_rx_channel; + rx_channel->base.set_carrier_action = rmt_rx_demodulate_carrier; + rx_channel->base.enable = rmt_rx_enable; + rx_channel->base.disable = rmt_rx_disable; + // return general channel handle + *ret_chan = &rx_channel->base; + ESP_LOGD(TAG, "new rx channel(%d,%d) at %p, gpio=%d, res=%uHz, hw_mem_base=%p, ping_pong_size=%d", + group_id, channel_id, rx_channel, config->gpio_num, rx_channel->base.resolution_hz, + rx_channel->base.hw_mem_base, rx_channel->ping_pong_symbols); + return ESP_OK; + +err: + if (rx_channel) { + rmt_rx_destory(rx_channel); + } + return ret; +} + +static esp_err_t rmt_del_rx_channel(rmt_channel_handle_t channel) +{ + rmt_rx_channel_t *rx_chan = __containerof(channel, rmt_rx_channel_t, base); + rmt_group_t *group = channel->group; + int group_id = group->group_id; + int channel_id = channel->channel_id; + ESP_LOGD(TAG, "del rx channel(%d,%d)", group_id, channel_id); + // recycle memory resource + ESP_RETURN_ON_ERROR(rmt_rx_destory(rx_chan), TAG, "destory rx channel failed"); + return ESP_OK; +} + +esp_err_t rmt_rx_register_event_callbacks(rmt_channel_handle_t channel, const rmt_rx_event_callbacks_t *cbs, void *user_data) +{ + ESP_RETURN_ON_FALSE(channel && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_RX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction"); + rmt_rx_channel_t *rx_chan = __containerof(channel, rmt_rx_channel_t, base); + +#if CONFIG_RMT_ISR_IRAM_SAFE + if (cbs->on_recv_done) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_recv_done), ESP_ERR_INVALID_ARG, TAG, "on_recv_done callback not in IRAM"); + } + if (user_data) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); + } +#endif + + rx_chan->on_recv_done = cbs->on_recv_done; + rx_chan->user_data = user_data; + return ESP_OK; +} + +esp_err_t rmt_receive(rmt_channel_handle_t channel, void *buffer, size_t buffer_size, const rmt_receive_config_t *config) +{ + ESP_RETURN_ON_FALSE(channel && buffer && buffer_size && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_RX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction"); + ESP_RETURN_ON_FALSE(channel->fsm == RMT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not in enable state"); + rmt_rx_channel_t *rx_chan = __containerof(channel, rmt_rx_channel_t, base); + + if (channel->dma_chan) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(buffer), ESP_ERR_INVALID_ARG, TAG, "buffer must locate in internal RAM for DMA use"); + } + if (channel->dma_chan) { + ESP_RETURN_ON_FALSE(buffer_size <= rx_chan->num_dma_nodes * RMT_DMA_DESC_BUF_MAX_SIZE, + ESP_ERR_INVALID_ARG, TAG, "buffer size exceeds DMA capacity"); + } + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + + // fill in the transaction descriptor + rmt_rx_trans_desc_t *t = &rx_chan->trans_desc; + t->buffer = buffer; + t->buffer_size = buffer_size; + t->received_symbol_num = 0; + t->copy_dest_off = 0; + + if (channel->dma_chan) { +#if SOC_RMT_SUPPORT_DMA + rmt_rx_mount_dma_buffer(rx_chan->dma_nodes, rx_chan->num_dma_nodes, buffer, buffer_size); + gdma_reset(channel->dma_chan); + gdma_start(channel->dma_chan, (intptr_t)rx_chan->dma_nodes); +#endif + } + + rx_chan->mem_off = 0; + portENTER_CRITICAL(&channel->spinlock); + // reset memory writer offset + rmt_ll_rx_reset_pointer(hal->regs, channel_id); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW); + // set sampling parameters of incoming signals + rmt_ll_rx_set_filter_thres(hal->regs, channel_id, ((uint64_t)group->resolution_hz * config->signal_range_min_ns) / 1000000000UL); + rmt_ll_rx_enable_filter(hal->regs, channel_id, config->signal_range_min_ns != 0); + rmt_ll_rx_set_idle_thres(hal->regs, channel_id, ((uint64_t)channel->resolution_hz * config->signal_range_max_ns) / 1000000000UL); + // turn on RMT RX machine + rmt_ll_rx_enable(hal->regs, channel_id, true); + portEXIT_CRITICAL(&channel->spinlock); + return ESP_OK; +} + +static esp_err_t rmt_rx_demodulate_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config) +{ +#if !SOC_RMT_SUPPORT_RX_DEMODULATION + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "rx demodulation not supported"); +#else + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int group_id = group->group_id; + int channel_id = channel->channel_id; + uint32_t real_frequency = 0; + + if (config && config->frequency_hz) { + // carrier demodulation module works base on channel clock (this is different from TX carrier modulation mode) + uint32_t total_ticks = channel->resolution_hz / config->frequency_hz; // Note this division operation will lose precision + uint32_t high_ticks = total_ticks * config->duty_cycle; + uint32_t low_ticks = total_ticks - high_ticks; + + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_rx_set_carrier_level(hal->regs, channel_id, !config->flags.polarity_active_low); + rmt_ll_rx_set_carrier_high_low_ticks(hal->regs, channel_id, high_ticks, low_ticks); + portEXIT_CRITICAL(&channel->spinlock); + // save real carrier frequency + real_frequency = channel->resolution_hz / (high_ticks + low_ticks); + } + + // enable/disable carrier demodulation + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_rx_enable_carrier_demodulation(hal->regs, channel_id, real_frequency > 0); + portEXIT_CRITICAL(&channel->spinlock); + + if (real_frequency > 0) { + ESP_LOGD(TAG, "enable carrier demodulation for channel(%d,%d), freq=%uHz", group_id, channel_id, real_frequency); + } else { + ESP_LOGD(TAG, "disable carrier demodulation for channel(%d, %d)", group_id, channel_id); + } + return ESP_OK; +#endif +} + +static esp_err_t rmt_rx_enable(rmt_channel_handle_t channel) +{ + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + + // acquire power manager lock + if (channel->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(channel->pm_lock), TAG, "acquire pm_lock failed"); + } + if (channel->dma_chan) { +#if SOC_RMT_SUPPORT_DMA + // enable the DMA access mode + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_rx_enable_dma(hal->regs, channel_id, true); + portEXIT_CRITICAL(&channel->spinlock); + + gdma_connect(channel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_RMT, 0)); +#endif // SOC_RMT_SUPPORT_DMA + } else { + portENTER_CRITICAL(&group->spinlock); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_RX_MASK(channel_id), true); + portEXIT_CRITICAL(&group->spinlock); + } + channel->fsm = RMT_FSM_ENABLE; + return ESP_OK; +} + +static esp_err_t rmt_rx_disable(rmt_channel_handle_t channel) +{ + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_rx_enable(hal->regs, channel_id, false); + portEXIT_CRITICAL(&channel->spinlock); + + if (channel->dma_chan) { +#if SOC_RMT_SUPPORT_DMA + gdma_stop(channel->dma_chan); + gdma_disconnect(channel->dma_chan); + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_rx_enable_dma(hal->regs, channel_id, false); + portEXIT_CRITICAL(&channel->spinlock); +#endif + } else { + portENTER_CRITICAL(&group->spinlock); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_RX_MASK(channel_id), false); + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_MASK(channel_id)); + portEXIT_CRITICAL(&group->spinlock); + } + + // release power manager lock + if (channel->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_release(channel->pm_lock), TAG, "release pm_lock failed"); + } + channel->fsm = RMT_FSM_INIT; + return ESP_OK; +} + +static size_t IRAM_ATTR rmt_copy_symbols(rmt_symbol_word_t *symbol_stream, size_t symbol_num, void *buffer, size_t offset, size_t buffer_size) +{ + size_t mem_want = symbol_num * sizeof(rmt_symbol_word_t); + size_t mem_have = buffer_size - offset; + size_t copy_size = MIN(mem_want, mem_have); + // do memory copy + memcpy(buffer + offset, symbol_stream, copy_size); + return copy_size; +} + +static bool IRAM_ATTR rmt_isr_handle_rx_done(rmt_rx_channel_t *rx_chan) +{ + rmt_channel_t *channel = &rx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + rmt_rx_trans_desc_t *trans_desc = &rx_chan->trans_desc; + bool need_yield = false; + + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_DONE(channel_id)); + + portENTER_CRITICAL_ISR(&channel->spinlock); + // disable the RX engine, it will be enabled again when next time user calls `rmt_receive()` + rmt_ll_rx_enable(hal->regs, channel_id, false); + uint32_t offset = rmt_ll_rx_get_memory_writer_offset(hal->regs, channel_id); + // sanity check + assert(offset > rx_chan->mem_off); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_SW); + // copy the symbols to user space + size_t stream_symbols = offset - rx_chan->mem_off; + size_t copy_size = rmt_copy_symbols(channel->hw_mem_base + rx_chan->mem_off, stream_symbols, + trans_desc->buffer, trans_desc->copy_dest_off, trans_desc->buffer_size); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW); + portEXIT_CRITICAL_ISR(&channel->spinlock); + +#if !SOC_RMT_SUPPORT_RX_PINGPONG + // for chips doesn't support ping-pong RX, we should check whether the receiver has encountered with a long frame, + // whose length is longer than the channel capacity + if (rmt_ll_rx_get_interrupt_status_raw(hal->regs, channel_id) & RMT_LL_EVENT_RX_ERROR(channel_id)) { + portENTER_CRITICAL_ISR(&channel->spinlock); + rmt_ll_rx_reset_pointer(hal->regs, channel_id); + portEXIT_CRITICAL_ISR(&channel->spinlock); + // this clear operation can only take effect after we copy out the received data and reset the pointer + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_ERROR(channel_id)); + ESP_DRAM_LOGE(TAG, "hw buffer too small, received symbols truncated"); + } +#endif // !SOC_RMT_SUPPORT_RX_PINGPONG + + // check whether all symbols are copied + if (copy_size != stream_symbols * sizeof(rmt_symbol_word_t)) { + ESP_DRAM_LOGE(TAG, "user buffer too small, received symbols truncated"); + } + trans_desc->copy_dest_off += copy_size; + trans_desc->received_symbol_num += copy_size / sizeof(rmt_symbol_word_t); + // notify the user with receive RMT symbols + if (rx_chan->on_recv_done) { + rmt_rx_done_event_data_t edata = { + .received_symbols = trans_desc->buffer, + .num_symbols = trans_desc->received_symbol_num, + }; + if (rx_chan->on_recv_done(channel, &edata, rx_chan->user_data)) { + need_yield = true; + } + } + return need_yield; +} + +#if SOC_RMT_SUPPORT_RX_PINGPONG +static bool IRAM_ATTR rmt_isr_handle_rx_threshold(rmt_rx_channel_t *rx_chan) +{ + rmt_channel_t *channel = &rx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + rmt_rx_trans_desc_t *trans_desc = &rx_chan->trans_desc; + + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_RX_THRES(channel_id)); + + portENTER_CRITICAL_ISR(&channel->spinlock); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_SW); + // copy the symbols to user space + size_t copy_size = rmt_copy_symbols(channel->hw_mem_base + rx_chan->mem_off, rx_chan->ping_pong_symbols, + trans_desc->buffer, trans_desc->copy_dest_off, trans_desc->buffer_size); + rmt_ll_rx_set_mem_owner(hal->regs, channel_id, RMT_LL_MEM_OWNER_HW); + portEXIT_CRITICAL_ISR(&channel->spinlock); + + // check whether all symbols are copied + if (copy_size != rx_chan->ping_pong_symbols * sizeof(rmt_symbol_word_t)) { + ESP_DRAM_LOGE(TAG, "received symbols truncated"); + } + trans_desc->copy_dest_off += copy_size; + trans_desc->received_symbol_num += copy_size / sizeof(rmt_symbol_word_t); + // update the hw memory offset, where stores the next RMT symbols to copy + rx_chan->mem_off = rx_chan->ping_pong_symbols - rx_chan->mem_off; + + return false; +} +#endif // SOC_RMT_SUPPORT_RX_PINGPONG + +static void IRAM_ATTR rmt_rx_default_isr(void *args) +{ + rmt_rx_channel_t *rx_chan = (rmt_rx_channel_t *)args; + rmt_channel_t *channel = &rx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + bool need_yield = false; + + uint32_t status = rmt_ll_rx_get_interrupt_status(hal->regs, channel_id); + +#if SOC_RMT_SUPPORT_RX_PINGPONG + // RX threshold interrupt + if (status & RMT_LL_EVENT_RX_THRES(channel_id)) { + if (rmt_isr_handle_rx_threshold(rx_chan)) { + need_yield = true; + } + } +#endif // SOC_RMT_SUPPORT_RX_PINGPONG + + // RX end interrupt + if (status & RMT_LL_EVENT_RX_DONE(channel_id)) { + if (rmt_isr_handle_rx_done(rx_chan)) { + need_yield = true; + } + } + + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +#if SOC_RMT_SUPPORT_DMA +static size_t IRAM_ATTR rmt_rx_get_received_symbol_num_from_dma(dma_descriptor_t *desc) +{ + size_t received_bytes = 0; + while (desc) { + received_bytes += desc->dw0.length; + desc = desc->next; + } + received_bytes = ALIGN_UP(received_bytes, sizeof(rmt_symbol_word_t)); + return received_bytes / sizeof(rmt_symbol_word_t); +} + +static bool IRAM_ATTR rmt_dma_rx_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) +{ + bool need_yield = false; + rmt_rx_channel_t *rx_chan = (rmt_rx_channel_t *)user_data; + rmt_channel_t *channel = &rx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + rmt_rx_trans_desc_t *trans_desc = &rx_chan->trans_desc; + uint32_t channel_id = channel->channel_id; + + portENTER_CRITICAL_ISR(&channel->spinlock); + // disable the RX engine, it will be enabled again in the next `rmt_receive()` + rmt_ll_rx_enable(hal->regs, channel_id, false); + portEXIT_CRITICAL_ISR(&channel->spinlock); + + if (rx_chan->on_recv_done) { + rmt_rx_done_event_data_t edata = { + .received_symbols = trans_desc->buffer, + .num_symbols = rmt_rx_get_received_symbol_num_from_dma(rx_chan->dma_nodes), + }; + if (rx_chan->on_recv_done(channel, &edata, rx_chan->user_data)) { + need_yield = true; + } + } + return need_yield; +} +#endif // SOC_RMT_SUPPORT_DMA diff --git a/components/driver/rmt/rmt_tx.c b/components/driver/rmt/rmt_tx.c new file mode 100644 index 0000000000..679148af67 --- /dev/null +++ b/components/driver/rmt/rmt_tx.c @@ -0,0 +1,1043 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include "sdkconfig.h" +#if CONFIG_RMT_ENABLE_DEBUG_LOG +// The local log level must be defined before including esp_log.h +// Set the maximum log level for this source file +#define LOG_LOCAL_LEVEL ESP_LOG_DEBUG +#endif +#include "esp_log.h" +#include "esp_check.h" +#include "esp_rom_gpio.h" +#include "soc/rmt_periph.h" +#include "soc/rtc.h" +#include "hal/rmt_ll.h" +#include "hal/gpio_hal.h" +#include "driver/gpio.h" +#include "driver/rmt_tx.h" +#include "rmt_private.h" + +static const char *TAG = "rmt"; + +struct rmt_sync_manager_t { + rmt_group_t *group; // which group the synchro belongs to + uint32_t channel_mask; // Mask of channels that are managed + size_t array_size; // Size of the `tx_channel_array` + rmt_channel_handle_t tx_channel_array[]; // Array of TX channels that are managed +}; + +static esp_err_t rmt_del_tx_channel(rmt_channel_handle_t channel); +static esp_err_t rmt_tx_modulate_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config); +static esp_err_t rmt_tx_enable(rmt_channel_handle_t channel); +static esp_err_t rmt_tx_disable(rmt_channel_handle_t channel); +static void rmt_tx_default_isr(void *args); + +#if SOC_RMT_SUPPORT_DMA +static bool rmt_dma_tx_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data); + +static esp_err_t rmt_tx_init_dma_link(rmt_tx_channel_t *tx_channel, const rmt_tx_channel_config_t *config) +{ + rmt_symbol_word_t *dma_mem_base = heap_caps_calloc(1, sizeof(rmt_symbol_word_t) * config->mem_block_symbols, RMT_MEM_ALLOC_CAPS | MALLOC_CAP_DMA); + ESP_RETURN_ON_FALSE(dma_mem_base, ESP_ERR_NO_MEM, TAG, "no mem for tx DMA buffer"); + tx_channel->base.dma_mem_base = dma_mem_base; + for (int i = 0; i < RMT_DMA_NODES_PING_PONG; i++) { + // each descriptor shares half of the DMA buffer + tx_channel->dma_nodes[i].buffer = dma_mem_base + tx_channel->ping_pong_symbols * i; + tx_channel->dma_nodes[i].dw0.size = tx_channel->ping_pong_symbols * sizeof(rmt_symbol_word_t); + // the ownership will be switched to DMA in `rmt_tx_do_transaction()` + tx_channel->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; + // each node can generate the DMA eof interrupt, and the driver will do a ping-pong trick in the eof callback + tx_channel->dma_nodes[i].dw0.suc_eof = 1; + } + gdma_channel_alloc_config_t dma_chan_config = { + .direction = GDMA_CHANNEL_DIRECTION_TX, + }; + ESP_RETURN_ON_ERROR(gdma_new_channel(&dma_chan_config, &tx_channel->base.dma_chan), TAG, "allocate TX DMA channel failed"); + gdma_strategy_config_t gdma_strategy_conf = { + .auto_update_desc = true, + .owner_check = true, + }; + gdma_apply_strategy(tx_channel->base.dma_chan, &gdma_strategy_conf); + gdma_tx_event_callbacks_t cbs = { + .on_trans_eof = rmt_dma_tx_eof_cb, + }; + gdma_register_tx_event_callbacks(tx_channel->base.dma_chan, &cbs, tx_channel); + return ESP_OK; +} +#endif // SOC_RMT_SUPPORT_DMA + +static esp_err_t rmt_tx_register_to_group(rmt_tx_channel_t *tx_channel, const rmt_tx_channel_config_t *config) +{ + size_t mem_block_num = 0; + // start to search for a free channel + // a channel can take up its neighbour's memory block, so the neighbour channel won't work, we should skip these "invaded" ones + int channel_scan_start = RMT_TX_CHANNEL_OFFSET_IN_GROUP; + int channel_scan_end = RMT_TX_CHANNEL_OFFSET_IN_GROUP + SOC_RMT_TX_CANDIDATES_PER_GROUP; + if (config->flags.with_dma) { + // for DMA mode, the memory block number is always 1; for non-DMA mode, memory block number is configured by user + mem_block_num = 1; + // Only the last channel has the DMA capability + channel_scan_start = RMT_TX_CHANNEL_OFFSET_IN_GROUP + SOC_RMT_TX_CANDIDATES_PER_GROUP - 1; + tx_channel->ping_pong_symbols = config->mem_block_symbols / 2; + } else { + // one channel can occupy multiple memory blocks + mem_block_num = config->mem_block_symbols / SOC_RMT_MEM_WORDS_PER_CHANNEL; + if (mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL < config->mem_block_symbols) { + mem_block_num++; + } + tx_channel->ping_pong_symbols = mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL / 2; + } + tx_channel->base.mem_block_num = mem_block_num; + + // search free channel and then register to the group + // memory blocks used by one channel must be continuous + uint32_t channel_mask = (1 << mem_block_num) - 1; + rmt_group_t *group = NULL; + int channel_id = -1; + for (int i = 0; i < SOC_RMT_GROUPS; i++) { + group = rmt_acquire_group_handle(i); + ESP_RETURN_ON_FALSE(group, ESP_ERR_NO_MEM, TAG, "no mem for group (%d)", i); + portENTER_CRITICAL(&group->spinlock); + for (int j = channel_scan_start; j < channel_scan_end; j++) { + if (!(group->occupy_mask & (channel_mask << j))) { + group->occupy_mask |= (channel_mask << j); + // the channel ID should index from 0 + channel_id = j - RMT_TX_CHANNEL_OFFSET_IN_GROUP; + group->tx_channels[channel_id] = tx_channel; + break; + } + } + portEXIT_CRITICAL(&group->spinlock); + if (channel_id < 0) { + // didn't find a capable channel in the group, don't forget to release the group handle + rmt_release_group_handle(group); + group = NULL; + } else { + tx_channel->base.channel_id = channel_id; + tx_channel->base.channel_mask = channel_mask; + tx_channel->base.group = group; + break; + } + } + ESP_RETURN_ON_FALSE(channel_id >= 0, ESP_ERR_NOT_FOUND, TAG, "no free tx channels"); + return ESP_OK; +} + +static void rmt_tx_unregister_from_group(rmt_channel_t *channel, rmt_group_t *group) +{ + portENTER_CRITICAL(&group->spinlock); + group->tx_channels[channel->channel_id] = NULL; + group->occupy_mask &= ~(channel->channel_mask << (channel->channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP)); + portEXIT_CRITICAL(&group->spinlock); + // channel has a reference on group, release it now + rmt_release_group_handle(group); +} + +static esp_err_t rmt_tx_create_trans_queue(rmt_tx_channel_t *tx_channel, const rmt_tx_channel_config_t *config) +{ + tx_channel->queue_size = config->trans_queue_depth; + // the queue only saves transaction description pointers + tx_channel->queues_storage = heap_caps_calloc(config->trans_queue_depth * RMT_TX_QUEUE_MAX, sizeof(rmt_tx_trans_desc_t *), RMT_MEM_ALLOC_CAPS); + ESP_RETURN_ON_FALSE(tx_channel->queues_storage, ESP_ERR_NO_MEM, TAG, "no mem for queue storage"); + rmt_tx_trans_desc_t **pp_trans_desc = (rmt_tx_trans_desc_t **)tx_channel->queues_storage; + for (int i = 0; i < RMT_TX_QUEUE_MAX; i++) { + tx_channel->trans_queues[i] = xQueueCreateStatic(config->trans_queue_depth, sizeof(rmt_tx_trans_desc_t *), + (uint8_t *)pp_trans_desc, &tx_channel->trans_queue_structs[i]); + pp_trans_desc += config->trans_queue_depth; + // sanity check + assert(tx_channel->trans_queues[i]); + } + // initialize the ready queue + rmt_tx_trans_desc_t *p_trans_desc = NULL; + for (int i = 0; i < config->trans_queue_depth; i++) { + p_trans_desc = &tx_channel->trans_desc_pool[i]; + ESP_RETURN_ON_FALSE(xQueueSend(tx_channel->trans_queues[RMT_TX_QUEUE_READY], &p_trans_desc, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + } + return ESP_OK; +} + +static esp_err_t rmt_tx_destory(rmt_tx_channel_t *tx_channel) +{ + if (tx_channel->base.intr) { + ESP_RETURN_ON_ERROR(esp_intr_free(tx_channel->base.intr), TAG, "delete interrupt service failed"); + } + if (tx_channel->base.pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_delete(tx_channel->base.pm_lock), TAG, "delete pm_lock failed"); + } +#if SOC_RMT_SUPPORT_DMA + if (tx_channel->base.dma_chan) { + ESP_RETURN_ON_ERROR(gdma_del_channel(tx_channel->base.dma_chan), TAG, "delete dma channel failed"); + } +#endif // SOC_RMT_SUPPORT_DMA + for (int i = 0; i < RMT_TX_QUEUE_MAX; i++) { + if (tx_channel->trans_queues[i]) { + vQueueDelete(tx_channel->trans_queues[i]); + } + } + if (tx_channel->queues_storage) { + free(tx_channel->queues_storage); + } + if (tx_channel->base.dma_mem_base) { + free(tx_channel->base.dma_mem_base); + } + if (tx_channel->base.group) { + // de-register channel from RMT group + rmt_tx_unregister_from_group(&tx_channel->base, tx_channel->base.group); + } + free(tx_channel); + return ESP_OK; +} + +esp_err_t rmt_new_tx_channel(const rmt_tx_channel_config_t *config, rmt_channel_handle_t *ret_chan) +{ +#if CONFIG_RMT_ENABLE_DEBUG_LOG + esp_log_level_set(TAG, ESP_LOG_DEBUG); +#endif + esp_err_t ret = ESP_OK; + rmt_tx_channel_t *tx_channel = NULL; + ESP_GOTO_ON_FALSE(config && ret_chan && config->resolution_hz && config->trans_queue_depth, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + ESP_GOTO_ON_FALSE(GPIO_IS_VALID_GPIO(config->gpio_num), ESP_ERR_INVALID_ARG, err, TAG, "invalid GPIO number"); + ESP_GOTO_ON_FALSE((config->mem_block_symbols & 0x01) == 0 && config->mem_block_symbols >= SOC_RMT_MEM_WORDS_PER_CHANNEL, + ESP_ERR_INVALID_ARG, err, TAG, "mem_block_symbols must be even and at least %d", SOC_RMT_MEM_WORDS_PER_CHANNEL); +#if SOC_RMT_SUPPORT_DMA + // we only support 2 nodes ping-pong, if the configured memory block size needs more than two DMA descriptors, should treat it as invalid + ESP_GOTO_ON_FALSE(config->mem_block_symbols <= RMT_DMA_DESC_BUF_MAX_SIZE * RMT_DMA_NODES_PING_PONG, ESP_ERR_INVALID_ARG, err, TAG, + "mem_block_symbols can't exceed %d", RMT_DMA_DESC_BUF_MAX_SIZE * RMT_DMA_NODES_PING_PONG); +#else + ESP_GOTO_ON_FALSE(config->flags.with_dma == 0, ESP_ERR_NOT_SUPPORTED, err, TAG, "DMA not supported"); +#endif + + // malloc channel memory + uint32_t mem_caps = RMT_MEM_ALLOC_CAPS; + if (config->flags.with_dma) { + // DMA descriptors must be placed in internal SRAM + mem_caps |= MALLOC_CAP_INTERNAL | MALLOC_CAP_DMA; + } + tx_channel = heap_caps_calloc(1, sizeof(rmt_tx_channel_t) + sizeof(rmt_tx_trans_desc_t) * config->trans_queue_depth, mem_caps); + ESP_GOTO_ON_FALSE(tx_channel, ESP_ERR_NO_MEM, err, TAG, "no mem for tx channel"); + // create transaction queues + ESP_GOTO_ON_ERROR(rmt_tx_create_trans_queue(tx_channel, config), err, TAG, "install trans queues failed"); + // register the channel to group + ESP_GOTO_ON_ERROR(rmt_tx_register_to_group(tx_channel, config), err, TAG, "register channel failed"); + rmt_group_t *group = tx_channel->base.group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = tx_channel->base.channel_id; + int group_id = group->group_id; + // select the clock source + ESP_GOTO_ON_ERROR(rmt_select_periph_clock(&tx_channel->base, config->clk_src), err, TAG, "set group clock failed"); + + // reset channel, make sure the TX engine is not working, and events are cleared + portENTER_CRITICAL(&group->spinlock); + rmt_hal_tx_channel_reset(&group->hal, channel_id); + portEXIT_CRITICAL(&group->spinlock); + + // install interrupt service + // interrupt is mandatory to run basic RMT transactions, so it's not lazy installed in `rmt_tx_register_event_callbacks()` + int isr_flags = RMT_INTR_ALLOC_FLAG; + ret = esp_intr_alloc_intrstatus(rmt_periph_signals.groups[group_id].irq, isr_flags, + (uint32_t)rmt_ll_get_interrupt_status_reg(hal->regs), + RMT_LL_EVENT_TX_MASK(channel_id), rmt_tx_default_isr, tx_channel, &tx_channel->base.intr); + ESP_GOTO_ON_ERROR(ret, err, TAG, "install tx interrupt failed"); + // install DMA service +#if SOC_RMT_SUPPORT_DMA + if (config->flags.with_dma) { + ESP_GOTO_ON_ERROR(rmt_tx_init_dma_link(tx_channel, config), err, TAG, "install tx DMA failed"); + } +#endif + // set channel clock resolution + uint32_t real_div = group->resolution_hz / config->resolution_hz; + rmt_ll_tx_set_channel_clock_div(hal->regs, channel_id, real_div); + // resolution lost due to division, calculate the real resolution + tx_channel->base.resolution_hz = group->resolution_hz / real_div; + if (tx_channel->base.resolution_hz != config->resolution_hz) { + ESP_LOGW(TAG, "channel resolution loss, real=%u", tx_channel->base.resolution_hz); + } + + rmt_ll_tx_set_mem_blocks(hal->regs, channel_id, tx_channel->base.mem_block_num); + // set limit threshold, after transmit ping_pong_symbols size, an interrupt event would be generated + rmt_ll_tx_set_limit(hal->regs, channel_id, tx_channel->ping_pong_symbols); + // disable carrier modulation by default, can reenable by `rmt_apply_carrier()` + rmt_ll_tx_enable_carrier_modulation(hal->regs, channel_id, false); + // idle level is determind by eof encoder, not set to a fixed value + rmt_ll_tx_fix_idle_level(hal->regs, channel_id, 0, false); + // always enable tx wrap, both DMA mode and ping-pong mode rely this feature + rmt_ll_tx_enable_wrap(hal->regs, channel_id, true); + + // GPIO Matrix/MUX configuration + tx_channel->base.gpio_num = config->gpio_num; + gpio_config_t gpio_conf = { + .intr_type = GPIO_INTR_DISABLE, + // also enable the input path is `io_loop_back` is on, this is useful for debug + .mode = GPIO_MODE_OUTPUT | (config->flags.io_loop_back ? GPIO_MODE_INPUT : 0), + .pull_down_en = false, + .pull_up_en = true, + .pin_bit_mask = 1ULL << config->gpio_num, + }; + ESP_GOTO_ON_ERROR(gpio_config(&gpio_conf), err, TAG, "config GPIO failed"); + esp_rom_gpio_connect_out_signal(config->gpio_num, + rmt_periph_signals.groups[group_id].channels[channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP].tx_sig, + config->flags.invert_out, false); + gpio_hal_iomux_func_sel(GPIO_PIN_MUX_REG[config->gpio_num], PIN_FUNC_GPIO); + + tx_channel->base.direction = RMT_CHANNEL_DIRECTION_TX; + tx_channel->base.fsm = RMT_FSM_INIT; + tx_channel->base.hw_mem_base = &RMTMEM + (channel_id + RMT_TX_CHANNEL_OFFSET_IN_GROUP) * SOC_RMT_MEM_WORDS_PER_CHANNEL; + tx_channel->base.spinlock = (portMUX_TYPE)portMUX_INITIALIZER_UNLOCKED; + // polymorphic methods + tx_channel->base.del = rmt_del_tx_channel; + tx_channel->base.set_carrier_action = rmt_tx_modulate_carrier; + tx_channel->base.enable = rmt_tx_enable; + tx_channel->base.disable = rmt_tx_disable; + // return general channel handle + *ret_chan = &tx_channel->base; + ESP_LOGD(TAG, "new tx channel(%d,%d) at %p, gpio=%d, res=%uHz, hw_mem_base=%p, dma_mem_base=%p, ping_pong_size=%zu, queue_depth=%zu", + group_id, channel_id, tx_channel, config->gpio_num, tx_channel->base.resolution_hz, + tx_channel->base.hw_mem_base, tx_channel->base.dma_mem_base, tx_channel->ping_pong_symbols, tx_channel->queue_size); + return ESP_OK; + +err: + if (tx_channel) { + rmt_tx_destory(tx_channel); + } + return ret; +} + +static esp_err_t rmt_del_tx_channel(rmt_channel_handle_t channel) +{ + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_group_t *group = channel->group; + int group_id = group->group_id; + int channel_id = channel->channel_id; + ESP_LOGD(TAG, "del tx channel(%d,%d)", group_id, channel_id); + // recycle memory resource + ESP_RETURN_ON_ERROR(rmt_tx_destory(tx_chan), TAG, "destory tx channel failed"); + return ESP_OK; +} + +esp_err_t rmt_new_sync_manager(const rmt_sync_manager_config_t *config, rmt_sync_manager_handle_t *ret_synchro) +{ + esp_err_t ret = ESP_OK; + rmt_sync_manager_t *synchro = NULL; +#if !SOC_RMT_SUPPORT_TX_SYNCHRO + ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "sync manager not supported"); +#else + ESP_GOTO_ON_FALSE(config && ret_synchro && config->tx_channel_array && config->array_size, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + synchro = heap_caps_calloc(1, sizeof(rmt_sync_manager_t) + sizeof(rmt_channel_handle_t) * config->array_size, RMT_MEM_ALLOC_CAPS); + ESP_GOTO_ON_FALSE(synchro, ESP_ERR_NO_MEM, err, TAG, "no mem for sync manager"); + for (size_t i = 0; i < config->array_size; i++) { + synchro->tx_channel_array[i] = config->tx_channel_array[i]; + } + synchro->array_size = config->array_size; + + int group_id = config->tx_channel_array[0]->group->group_id; + // acquire group handle, increase reference count + rmt_group_t *group = rmt_acquire_group_handle(group_id); + // sanity check + assert(group); + synchro->group = group; + // calculate the mask of the channels to be managed + uint32_t channel_mask = 0; + rmt_channel_handle_t channel = NULL; + for (size_t i = 0; i < config->array_size; i++) { + channel = config->tx_channel_array[i]; + ESP_GOTO_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_TX, ESP_ERR_INVALID_ARG, err, TAG, "sync manager supports TX channel only"); + ESP_GOTO_ON_FALSE(channel->group == group, ESP_ERR_INVALID_ARG, err, TAG, "channels to be managed should locate in the same group"); + ESP_GOTO_ON_FALSE(channel->fsm == RMT_FSM_ENABLE, ESP_ERR_INVALID_STATE, err, TAG, "channel should be started before creating sync manager"); + channel_mask |= 1 << channel->channel_id; + } + synchro->channel_mask = channel_mask; + + // search and register sync manager to group + bool new_synchro = false; + portENTER_CRITICAL(&group->spinlock); + if (group->sync_manager == NULL) { + group->sync_manager = synchro; + new_synchro = true; + } + portEXIT_CRITICAL(&group->spinlock); + ESP_GOTO_ON_FALSE(new_synchro, ESP_ERR_NOT_FOUND, err, TAG, "no free sync manager in the group"); + + // enable sync manager + portENTER_CRITICAL(&group->spinlock); + rmt_ll_tx_enable_sync(group->hal.regs, true); + rmt_ll_tx_sync_group_add_channels(group->hal.regs, channel_mask); + rmt_ll_tx_reset_channels_clock_div(group->hal.regs, channel_mask); + // ensure the reading cursor of each channel is pulled back to the starting line + for (size_t i = 0; i < config->array_size; i++) { + rmt_ll_tx_reset_pointer(group->hal.regs, config->tx_channel_array[i]->channel_id); + } + portEXIT_CRITICAL(&group->spinlock); + + + *ret_synchro = synchro; + ESP_LOGD(TAG, "new sync manager at %p, with channel mask:%02x", synchro, synchro->channel_mask); + return ESP_OK; +#endif // !SOC_RMT_SUPPORT_TX_SYNCHRO + +err: + if (synchro) { + if (synchro->group) { + rmt_release_group_handle(synchro->group); + } + free(synchro); + } + return ret; +} + +esp_err_t rmt_sync_reset(rmt_sync_manager_handle_t synchro) +{ +#if !SOC_RMT_SUPPORT_TX_SYNCHRO + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "sync manager not supported"); +#else + ESP_RETURN_ON_FALSE(synchro, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + rmt_group_t *group = synchro->group; + + portENTER_CRITICAL(&group->spinlock); + rmt_ll_tx_reset_channels_clock_div(group->hal.regs, synchro->channel_mask); + for (size_t i = 0; i < synchro->array_size; i++) { + rmt_ll_tx_reset_pointer(group->hal.regs, synchro->tx_channel_array[i]->channel_id); + } + portEXIT_CRITICAL(&group->spinlock); + + return ESP_OK; +#endif // !SOC_RMT_SUPPORT_TX_SYNCHRO +} + +esp_err_t rmt_del_sync_manager(rmt_sync_manager_handle_t synchro) +{ +#if !SOC_RMT_SUPPORT_TX_SYNCHRO + ESP_RETURN_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, TAG, "sync manager not supported"); +#else + ESP_RETURN_ON_FALSE(synchro, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + rmt_group_t *group = synchro->group; + int group_id = group->group_id; + + portENTER_CRITICAL(&group->spinlock); + group->sync_manager = NULL; + // disable sync manager + rmt_ll_tx_enable_sync(group->hal.regs, false); + rmt_ll_tx_sync_group_remove_channels(group->hal.regs, synchro->channel_mask); + portEXIT_CRITICAL(&group->spinlock); + + free(synchro); + ESP_LOGD(TAG, "del sync manager in group(%d)", group_id); + rmt_release_group_handle(group); + return ESP_OK; +#endif // !SOC_RMT_SUPPORT_TX_SYNCHRO +} + +esp_err_t rmt_tx_register_event_callbacks(rmt_channel_handle_t channel, const rmt_tx_event_callbacks_t *cbs, void *user_data) +{ + ESP_RETURN_ON_FALSE(channel && cbs, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_TX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction"); + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + +#if CONFIG_RMT_ISR_IRAM_SAFE + if (cbs->on_trans_done) { + ESP_RETURN_ON_FALSE(esp_ptr_in_iram(cbs->on_trans_done), ESP_ERR_INVALID_ARG, TAG, "on_trans_done callback not in IRAM"); + } + if (user_data) { + ESP_RETURN_ON_FALSE(esp_ptr_internal(user_data), ESP_ERR_INVALID_ARG, TAG, "user context not in internal RAM"); + } +#endif + + tx_chan->on_trans_done = cbs->on_trans_done; + tx_chan->user_data = user_data; + return ESP_OK; +} + +esp_err_t rmt_transmit(rmt_channel_handle_t channel, rmt_encoder_t *encoder, const void *payload, size_t payload_bytes, const rmt_transmit_config_t *config) +{ + ESP_RETURN_ON_FALSE(channel && encoder && payload && payload_bytes && config, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + ESP_RETURN_ON_FALSE(channel->direction == RMT_CHANNEL_DIRECTION_TX, ESP_ERR_INVALID_ARG, TAG, "invalid channel direction"); + ESP_RETURN_ON_FALSE(channel->fsm == RMT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "channel not in enable state"); +#if !SOC_RMT_SUPPORT_TX_LOOP_COUNT + ESP_RETURN_ON_FALSE(config->loop_count <= 0, ESP_ERR_NOT_SUPPORTED, TAG, "loop count is not supported"); +#endif // !SOC_RMT_SUPPORT_TX_LOOP_COUNT + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_tx_trans_desc_t *t = NULL; + // acquire one transaction description from ready_queue or done_queue + if (tx_chan->num_trans_inflight < tx_chan->queue_size) { + xQueueReceive(tx_chan->trans_queues[RMT_TX_QUEUE_READY], &t, portMAX_DELAY); + } else { + xQueueReceive(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &t, portMAX_DELAY); + tx_chan->num_trans_inflight--; + } + // fill in the transaction descriptor + memset(t, 0, sizeof(rmt_tx_trans_desc_t)); + t->encoder = encoder; + t->payload = payload; + t->payload_bytes = payload_bytes; + t->loop_count = config->loop_count; + t->remain_loop_count = t->loop_count; + t->flags.eot_level = config->flags.eot_level; + + // send the transaction descriptor to queue + if (xQueueSend(tx_chan->trans_queues[RMT_TX_QUEUE_PROGRESS], &t, portMAX_DELAY) == pdTRUE) { + tx_chan->num_trans_inflight++; + } else { + // put the trans descriptor back to ready_queue + ESP_RETURN_ON_FALSE(xQueueSend(tx_chan->trans_queues[RMT_TX_QUEUE_READY], &t, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + } + + // we don't know which "transmission complete" event will be triggered, but must be one of them: trans_done, loop_done + // when we run at here, the interrupt status bit for tx_done or loop_end should already up (ensured by `rmt_tx_enable()`) + // that's why we can go into ISR as soon as we enable the interrupt bit + // in the ISR, we will fetch the transactions from trans_queue and start it + portENTER_CRITICAL(&group->spinlock); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id) | RMT_LL_EVENT_TX_LOOP_END(channel_id), true); + portEXIT_CRITICAL(&group->spinlock); + return ESP_OK; +} + +esp_err_t rmt_tx_wait_all_done(rmt_channel_handle_t channel, int timeout_ms) +{ + ESP_RETURN_ON_FALSE(channel, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + TickType_t wait_ticks = timeout_ms < 0 ? portMAX_DELAY : pdMS_TO_TICKS(timeout_ms); + // recycle all transaction that are on the fly + rmt_tx_trans_desc_t *t = NULL; + size_t num_trans_inflight = tx_chan->num_trans_inflight; + for (size_t i = 0; i < num_trans_inflight; i++) { + ESP_RETURN_ON_FALSE(xQueueReceive(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &t, wait_ticks) == pdTRUE, + ESP_ERR_TIMEOUT, TAG, "flush timeout"); + ESP_RETURN_ON_FALSE(xQueueSend(tx_chan->trans_queues[RMT_TX_QUEUE_READY], &t, 0) == pdTRUE, + ESP_ERR_INVALID_STATE, TAG, "ready queue full"); + tx_chan->num_trans_inflight--; + } + return ESP_OK; +} + +static void IRAM_ATTR rmt_tx_mark_eof(rmt_tx_channel_t *tx_chan) +{ + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + int channel_id = channel->channel_id; + rmt_symbol_word_t *mem_to = channel->dma_chan ? channel->dma_mem_base : channel->hw_mem_base; + rmt_tx_trans_desc_t *cur_trans = tx_chan->cur_trans; + dma_descriptor_t *desc = NULL; + + // a RMT word whose duration is zero means a "stop" pattern + mem_to[tx_chan->mem_off++] = (rmt_symbol_word_t) { + .duration0 = 0, + .level0 = cur_trans->flags.eot_level, + .duration1 = 0, + .level1 = cur_trans->flags.eot_level, + }; + + size_t off = 0; + if (channel->dma_chan) { + if (tx_chan->mem_off <= tx_chan->ping_pong_symbols) { + desc = &tx_chan->dma_nodes[0]; + off = tx_chan->mem_off; + } else { + desc = &tx_chan->dma_nodes[1]; + off = tx_chan->mem_off - tx_chan->ping_pong_symbols; + } + desc->dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_DMA; + desc->dw0.length = off * sizeof(rmt_symbol_word_t); + // break down the DMA descriptor link + desc->next = NULL; + } else { + portENTER_CRITICAL_ISR(&group->spinlock); + // This is the end of a sequence of encoding sessions, disable the threshold interrupt as no more data will be put into RMT memory block + rmt_ll_enable_interrupt(group->hal.regs, RMT_LL_EVENT_TX_THRES(channel_id), false); + portEXIT_CRITICAL_ISR(&group->spinlock); + } +} + +static size_t IRAM_ATTR rmt_encode_check_result(rmt_tx_channel_t *tx_chan, rmt_tx_trans_desc_t *t) +{ + rmt_encode_state_t encode_state = 0; + rmt_encoder_handle_t encoder = t->encoder; + size_t encoded_symbols = encoder->encode(encoder, &tx_chan->base, t->payload, t->payload_bytes, &encode_state); + if (encode_state & RMT_ENCODING_COMPLETE) { + t->flags.encoding_done = true; + // inserting EOF symbol if there's extra space + if (!(encode_state & RMT_ENCODING_MEM_FULL)) { + rmt_tx_mark_eof(tx_chan); + encoded_symbols += 1; + } + } + + // for loop transaction, the memory block should accommodate all encoded RMT symbols + if (t->loop_count != 0) { + if (unlikely(encoded_symbols > tx_chan->base.mem_block_num * SOC_RMT_MEM_WORDS_PER_CHANNEL)) { + ESP_DRAM_LOGE(TAG, "encoding artifacts can't exceed hw memory block for loop transmission"); + } + } + + return encoded_symbols; +} + +static void IRAM_ATTR rmt_tx_do_transaction(rmt_tx_channel_t *tx_chan, rmt_tx_trans_desc_t *t) +{ + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + +#if SOC_RMT_SUPPORT_DMA + if (channel->dma_chan) { + gdma_reset(channel->dma_chan); + // chain the descritpros into a ring, and will break it in `rmt_encode_eof()` + for (int i = 0; i < RMT_DMA_NODES_PING_PONG; i++) { + tx_chan->dma_nodes[i].next = &tx_chan->dma_nodes[i + 1]; + tx_chan->dma_nodes[i].dw0.owner = DMA_DESCRIPTOR_BUFFER_OWNER_CPU; + } + tx_chan->dma_nodes[1].next = &tx_chan->dma_nodes[0]; + } +#endif // SOC_RMT_SUPPORT_DMA + + // set transaction specific parameters + portENTER_CRITICAL_ISR(&channel->spinlock); + rmt_ll_tx_reset_pointer(hal->regs, channel_id); // reset pointer for new transaction + rmt_ll_tx_enable_loop(hal->regs, channel_id, t->loop_count != 0); +#if SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP + rmt_ll_tx_enable_loop_autostop(hal->regs, channel_id, true); +#endif // SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + rmt_ll_tx_reset_loop_count(hal->regs, channel_id); + rmt_ll_tx_enable_loop_count(hal->regs, channel_id, t->loop_count > 0); + // transfer loops in batches + if (t->remain_loop_count > 0) { + uint32_t this_loop_count = MIN(t->remain_loop_count, RMT_LL_MAX_LOOP_COUNT_PER_BATCH); + rmt_ll_tx_set_loop_count(hal->regs, channel_id, this_loop_count); + t->remain_loop_count -= this_loop_count; + } +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + portEXIT_CRITICAL_ISR(&channel->spinlock); + + // enable/disable specific interrupts + portENTER_CRITICAL_ISR(&group->spinlock); +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id), t->loop_count > 0); +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + // in DMA mode, DMA eof event plays the similar functionality to this threshold interrupt, so only enable it for non-DMA mode + if (!channel->dma_chan) { + // don't enable threshold interrupt with loop mode on + // threshold interrupt will be disabled in `rmt_encode_eof()` + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_THRES(channel_id), t->loop_count == 0); + // Threshold interrupt will be generated by accident, clear it before starting new transmission + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_THRES(channel_id)); + } + // don't generate trans done event for loop transmission + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id), t->loop_count == 0); + portEXIT_CRITICAL_ISR(&group->spinlock); + + // at the beginning of a new transaction, encoding memory offset should start from zero. + // It will increase in the encode function e.g. `rmt_encode_copy()` + tx_chan->mem_off = 0; + // use the full memory block for the beginning encoding session + tx_chan->mem_end = tx_chan->ping_pong_symbols * 2; + // perform the encoding session, return the number of encoded symbols + t->transmitted_symbol_num = rmt_encode_check_result(tx_chan, t); + // we're going to perform ping-pong operation, so the next encoding end position is the middle + tx_chan->mem_end = tx_chan->ping_pong_symbols; + +#if SOC_RMT_SUPPORT_DMA + if (channel->dma_chan) { + gdma_start(channel->dma_chan, (intptr_t)tx_chan->dma_nodes); + // delay a while, wait for DMA data going to RMT memory block + esp_rom_delay_us(1); + } +#endif + // turn on the TX machine + portENTER_CRITICAL_ISR(&channel->spinlock); + rmt_ll_tx_start(hal->regs, channel_id); + portEXIT_CRITICAL_ISR(&channel->spinlock); +} + +static esp_err_t rmt_tx_enable(rmt_channel_handle_t channel) +{ + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + // acquire power manager lock + if (channel->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_acquire(channel->pm_lock), TAG, "acquire pm_lock failed"); + } + + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_tx_reset_pointer(hal->regs, channel_id); + rmt_ll_tx_enable_loop(hal->regs, channel_id, false); +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + rmt_ll_tx_reset_loop_count(hal->regs, channel_id); + rmt_ll_tx_enable_loop_count(hal->regs, channel_id, false); +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + // trigger a quick trans done event by sending a EOF symbol, no signal should appear on the GPIO + tx_chan->cur_trans = NULL; + channel->hw_mem_base[0].val = 0; + rmt_ll_tx_start(hal->regs, channel_id); + portEXIT_CRITICAL(&channel->spinlock); + + // wait the RMT interrupt line goes active, we won't go into the ISR handler until we enable the `RMT_LL_EVENT_TX_DONE` interrupt + while (!(rmt_ll_tx_get_interrupt_status_raw(hal->regs, channel_id) & RMT_LL_EVENT_TX_DONE(channel_id))) {} +#if SOC_RMT_SUPPORT_DMA + if (channel->dma_chan) { + // enable the DMA access mode + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_tx_enable_dma(hal->regs, channel_id, true); + portEXIT_CRITICAL(&channel->spinlock); + + gdma_connect(channel->dma_chan, GDMA_MAKE_TRIGGER(GDMA_TRIG_PERIPH_RMT, 0)); + } +#endif // SOC_RMT_SUPPORT_DMA + + channel->fsm = RMT_FSM_ENABLE; + + // enable channel interrupt, dispatch transactions in ISR (in case there're transaction descriptors in the queue, then we should start them) + portENTER_CRITICAL(&group->spinlock); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id), true); + portEXIT_CRITICAL(&group->spinlock); + return ESP_OK; +} + +static esp_err_t rmt_tx_disable(rmt_channel_handle_t channel) +{ + rmt_tx_channel_t *tx_chan = __containerof(channel, rmt_tx_channel_t, base); + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int channel_id = channel->channel_id; + + portENTER_CRITICAL(&channel->spinlock); + // when this function called, the transaction might be middle-way, the output level when we stop the transmitter is nondeterministic, + // so we fix the idle level temporarily + rmt_ll_tx_fix_idle_level(hal->regs, channel->channel_id, tx_chan->cur_trans ? tx_chan->cur_trans->flags.eot_level : 0, true); + rmt_ll_tx_enable_loop(hal->regs, channel->channel_id, false); +#if SOC_RMT_SUPPORT_TX_ASYNC_STOP + rmt_ll_tx_stop(hal->regs, channel->channel_id); +#endif + portEXIT_CRITICAL(&channel->spinlock); + + portENTER_CRITICAL(&group->spinlock); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_MASK(channel_id), false); +#if !SOC_RMT_SUPPORT_TX_ASYNC_STOP + // we do a trick to stop the undergoing transmission + // stop interrupt, insert EOF marker to the RMT memory, polling the trans_done event + channel->hw_mem_base[0].val = 0; + while (!(rmt_ll_tx_get_interrupt_status_raw(hal->regs, channel_id) & RMT_LL_EVENT_TX_DONE(channel_id))) {} +#endif + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_MASK(channel_id)); + portEXIT_CRITICAL(&group->spinlock); + + portENTER_CRITICAL(&channel->spinlock); + // restore the idle level selection, to be determind by eof symbol + rmt_ll_tx_fix_idle_level(hal->regs, channel_id, 0, false); + portEXIT_CRITICAL(&channel->spinlock); + +#if SOC_RMT_SUPPORT_DMA + if (channel->dma_chan) { + gdma_stop(channel->dma_chan); + gdma_disconnect(channel->dma_chan); + + // disable DMA access mode + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_tx_enable_dma(hal->regs, channel_id, false); + portEXIT_CRITICAL(&channel->spinlock); + } +#endif + // recycle the interrupted transaction + if (tx_chan->cur_trans) { + xQueueSend(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &tx_chan->cur_trans, portMAX_DELAY); + // reset corresponding encoder + rmt_encoder_reset(tx_chan->cur_trans->encoder); + } + tx_chan->cur_trans = NULL; + + // release power manager lock + if (channel->pm_lock) { + ESP_RETURN_ON_ERROR(esp_pm_lock_release(channel->pm_lock), TAG, "release pm_lock failed"); + } + + channel->fsm = RMT_FSM_INIT; + return ESP_OK; +} + +static esp_err_t rmt_tx_modulate_carrier(rmt_channel_handle_t channel, const rmt_carrier_config_t *config) +{ + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + int group_id = group->group_id; + int channel_id = channel->channel_id; + uint32_t real_frequency = 0; + + if (config && config->frequency_hz) { + // carrier module works base on group clock + uint32_t total_ticks = group->resolution_hz / config->frequency_hz; // Note this division operation will lose precision + uint32_t high_ticks = total_ticks * config->duty_cycle; + uint32_t low_ticks = total_ticks - high_ticks; + + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_tx_set_carrier_level(hal->regs, channel_id, !config->flags.polarity_active_low); + rmt_ll_tx_set_carrier_high_low_ticks(hal->regs, channel_id, high_ticks, low_ticks); +#if SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY + rmt_ll_tx_enable_carrier_always_on(hal->regs, channel_id, config->flags.always_on); +#endif + portEXIT_CRITICAL(&channel->spinlock); + // save real carrier frequency + real_frequency = group->resolution_hz / total_ticks; + } + + // enable/disable carrier modulation + portENTER_CRITICAL(&channel->spinlock); + rmt_ll_tx_enable_carrier_modulation(hal->regs, channel_id, real_frequency > 0); + portEXIT_CRITICAL(&channel->spinlock); + + if (real_frequency > 0) { + ESP_LOGD(TAG, "enable carrier modulation for channel(%d,%d), freq=%uHz", group_id, channel_id, real_frequency); + } else { + ESP_LOGD(TAG, "disable carrier modulation for channel(%d,%d)", group_id, channel_id); + } + return ESP_OK; +} + +static bool IRAM_ATTR rmt_isr_handle_tx_threshold(rmt_tx_channel_t *tx_chan) +{ + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + + // continue pingpong transmission + rmt_tx_trans_desc_t *t = tx_chan->cur_trans; + size_t encoded_symbols = t->transmitted_symbol_num; + // encoding finished, only need to send the EOF symbol + if (t->flags.encoding_done) { + rmt_tx_mark_eof(tx_chan); + encoded_symbols += 1; + } else { + encoded_symbols += rmt_encode_check_result(tx_chan, t); + } + t->transmitted_symbol_num = encoded_symbols; + tx_chan->mem_end = tx_chan->ping_pong_symbols * 3 - tx_chan->mem_end; // mem_end equals to either ping_pong_symbols or ping_pong_symbols*2 + + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_THRES(channel_id)); + + return false; +} + +static bool IRAM_ATTR rmt_isr_handle_tx_done(rmt_tx_channel_t *tx_chan) +{ + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + BaseType_t awoken = pdFALSE; + rmt_tx_trans_desc_t *trans_desc = NULL; + bool need_yield = false; + + portENTER_CRITICAL_ISR(&group->spinlock); + // disable interrupt temporarily, re-enable it when there is transaction unhandled in the queue + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id), false); + portEXIT_CRITICAL_ISR(&group->spinlock); + + trans_desc = tx_chan->cur_trans; + // process finished transaction + if (trans_desc) { + // don't care of the tx done event for any undergoing loop transaction + // mostly it's triggered when a loop transmission is undergoing and user calls `rmt_transmit()` where tx done interrupt is generated by accident + if (trans_desc->loop_count != 0) { + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id)); + return need_yield; + } + if (tx_chan->on_trans_done) { + rmt_tx_done_event_data_t edata = { + .num_symbols = trans_desc->transmitted_symbol_num, + }; + if (tx_chan->on_trans_done(channel, &edata, tx_chan->user_data)) { + need_yield = true; + } + } + // move transaction to done_queue + xQueueSendFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &trans_desc, &awoken); + if (awoken == pdTRUE) { + need_yield = true; + } + } + // fetch new transaction description from trans_queue + if (xQueueReceiveFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_PROGRESS], &trans_desc, &awoken) == pdTRUE) { + // update current transaction + tx_chan->cur_trans = trans_desc; + + portENTER_CRITICAL_ISR(&group->spinlock); + // only clear the trans done status when we're sure there still remains transaction to handle + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id)); + // enable interrupt again, because the new transaction can trigger another trans done event + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id), trans_desc->loop_count == 0); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id), trans_desc->loop_count > 0); + portEXIT_CRITICAL_ISR(&group->spinlock); + + // begin a new transaction + rmt_tx_do_transaction(tx_chan, trans_desc); + } else { // No transactions left in the queue + // don't clear interrupt status, so when next time user push new transaction to the queue and call esp_intr_enable, + // we can go to this ISR handler again + tx_chan->cur_trans = NULL; + } + if (awoken == pdTRUE) { + need_yield = true; + } + + return need_yield; +} + +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT +static bool IRAM_ATTR rmt_isr_handle_tx_loop_end(rmt_tx_channel_t *tx_chan) +{ + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + BaseType_t awoken = pdFALSE; + rmt_tx_trans_desc_t *trans_desc = NULL; + bool need_yield = false; + + trans_desc = tx_chan->cur_trans; + if (trans_desc) { +#if !SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP + portENTER_CRITICAL_ISR(&channel->spinlock); + // This is a workaround for chips that don't support auto stop + // Although we stop the transaction immediately in ISR handler, it's still possible that some rmt symbols have sneaked out + rmt_ll_tx_stop(hal->regs, channel_id); + portEXIT_CRITICAL_ISR(&channel->spinlock); +#endif // SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP + // continue unfinished loop transaction + if (trans_desc->remain_loop_count) { + uint32_t this_loop_count = MIN(trans_desc->remain_loop_count, RMT_LL_MAX_LOOP_COUNT_PER_BATCH); + trans_desc->remain_loop_count -= this_loop_count; + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id)); + portENTER_CRITICAL_ISR(&channel->spinlock); + rmt_ll_tx_set_loop_count(hal->regs, channel_id, this_loop_count); + rmt_ll_tx_reset_pointer(hal->regs, channel_id); + // continue the loop transmission, don't need to fill the RMT symbols again, just restart the engine + rmt_ll_tx_start(hal->regs, channel_id); + portEXIT_CRITICAL_ISR(&channel->spinlock); + return need_yield; + } else { + if (tx_chan->on_trans_done) { + rmt_tx_done_event_data_t edata = { + .num_symbols = trans_desc->transmitted_symbol_num, + }; + if (tx_chan->on_trans_done(channel, &edata, tx_chan->user_data)) { + need_yield = true; + } + } + // move transaction to done_queue + xQueueSendFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_COMPLETE], &trans_desc, &awoken); + if (awoken == pdTRUE) { + need_yield = true; + } + } + } + // trans_done and loop_done should be considered as one "transmission complete" + // but sometimes the trans done event might also be triggered together with loop done event, by accident, so clear it first + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id)); + portENTER_CRITICAL_ISR(&group->spinlock); + // disable interrupt temporarily, re-enable it when there is transaction unhandled in the queue + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id), false); + portEXIT_CRITICAL_ISR(&group->spinlock); + + // fetch new transaction description from trans_queue + if (xQueueReceiveFromISR(tx_chan->trans_queues[RMT_TX_QUEUE_PROGRESS], &trans_desc, &awoken) == pdTRUE) { + tx_chan->cur_trans = trans_desc; + // clear the loop end status when we're sure there still remains transaction to handle + rmt_ll_clear_interrupt_status(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id)); + + portENTER_CRITICAL_ISR(&group->spinlock); + // enable interrupt again, because the new transaction can trigger new trans done event + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_DONE(channel_id), trans_desc->loop_count == 0); + rmt_ll_enable_interrupt(hal->regs, RMT_LL_EVENT_TX_LOOP_END(channel_id), trans_desc->loop_count > 0); + portEXIT_CRITICAL_ISR(&group->spinlock); + + // begin a new transaction + rmt_tx_do_transaction(tx_chan, trans_desc); + } else { // No transactions left in the queue + // don't clear interrupt status, so when next time user push new transaction to the queue and call esp_intr_enable, + // we can go into ISR handler again + tx_chan->cur_trans = NULL; + } + if (awoken == pdTRUE) { + need_yield = true; + } + return need_yield; +} +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + +static void IRAM_ATTR rmt_tx_default_isr(void *args) +{ + rmt_tx_channel_t *tx_chan = (rmt_tx_channel_t *)args; + rmt_channel_t *channel = &tx_chan->base; + rmt_group_t *group = channel->group; + rmt_hal_context_t *hal = &group->hal; + uint32_t channel_id = channel->channel_id; + bool need_yield = false; + + uint32_t status = rmt_ll_tx_get_interrupt_status(hal->regs, channel_id); + + // Tx threshold interrupt + if (status & RMT_LL_EVENT_TX_THRES(channel_id)) { + if (rmt_isr_handle_tx_threshold(tx_chan)) { + need_yield = true; + } + } + + // Tx end interrupt + if (status & RMT_LL_EVENT_TX_DONE(channel_id)) { + if (rmt_isr_handle_tx_done(tx_chan)) { + need_yield = true; + } + } + +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + // Tx loop end interrupt + if (status & RMT_LL_EVENT_TX_LOOP_END(channel_id)) { + if (rmt_isr_handle_tx_loop_end(tx_chan)) { + need_yield = true; + } + } +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + + if (need_yield) { + portYIELD_FROM_ISR(); + } +} + +#if SOC_RMT_SUPPORT_DMA +static bool IRAM_ATTR rmt_dma_tx_eof_cb(gdma_channel_handle_t dma_chan, gdma_event_data_t *event_data, void *user_data) +{ + rmt_tx_channel_t *tx_chan = (rmt_tx_channel_t *)user_data; + dma_descriptor_t *eof_desc = (dma_descriptor_t *)event_data->tx_eof_desc_addr; + // if the DMA descriptor link is still a ring (i.e. hasn't broken down by `rmt_tx_mark_eof()`), then we treat it as a valid ping-pong event + if (eof_desc->next && eof_desc->next->next) { + // continue pingpong transmission + rmt_tx_trans_desc_t *t = tx_chan->cur_trans; + size_t encoded_symbols = t->transmitted_symbol_num; + if (t->flags.encoding_done) { + rmt_tx_mark_eof(tx_chan); + encoded_symbols += 1; + } else { + encoded_symbols += rmt_encode_check_result(tx_chan, t); + } + t->transmitted_symbol_num = encoded_symbols; + tx_chan->mem_end = tx_chan->ping_pong_symbols * 3 - tx_chan->mem_end; // mem_end equals to either ping_pong_symbols or ping_pong_symbols*2 + // tell DMA that we have a new descriptor attached + gdma_append(dma_chan); + } + return false; +} +#endif // SOC_RMT_SUPPORT_DMA diff --git a/components/driver/test_apps/rmt/CMakeLists.txt b/components/driver/test_apps/rmt/CMakeLists.txt new file mode 100644 index 0000000000..4523465b2f --- /dev/null +++ b/components/driver/test_apps/rmt/CMakeLists.txt @@ -0,0 +1,18 @@ +# This is the project CMakeLists.txt file for the test subproject +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(rmt_test) + +if(CONFIG_COMPILER_DUMP_RTL_FILES) + add_custom_target(check_test_app_sections ALL + COMMAND ${PYTHON} $ENV{IDF_PATH}/tools/ci/check_callgraph.py + --rtl-dir ${CMAKE_BINARY_DIR}/esp-idf/driver/ + --elf-file ${CMAKE_BINARY_DIR}/rmt_test.elf + find-refs + --from-sections=.iram0.text + --to-sections=.flash.text,.flash.rodata + --exit-code + DEPENDS ${elf} + ) +endif() diff --git a/components/driver/test_apps/rmt/README.md b/components/driver/test_apps/rmt/README.md new file mode 100644 index 0000000000..3f7a0a04f0 --- /dev/null +++ b/components/driver/test_apps/rmt/README.md @@ -0,0 +1,2 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | +| ----------------- | ----- | -------- | -------- | -------- | diff --git a/components/driver/test_apps/rmt/main/CMakeLists.txt b/components/driver/test_apps/rmt/main/CMakeLists.txt new file mode 100644 index 0000000000..84ab9ed8dd --- /dev/null +++ b/components/driver/test_apps/rmt/main/CMakeLists.txt @@ -0,0 +1,8 @@ +set(srcs "test_app_main.c" + "test_rmt_common.c" + "test_rmt_tx.c" + "test_rmt_rx.c" + "test_util_rmt_encoders.c") + +idf_component_register(SRCS "${srcs}" + WHOLE_ARCHIVE) diff --git a/components/driver/test_apps/rmt/main/test_app_main.c b/components/driver/test_apps/rmt/main/test_app_main.c new file mode 100644 index 0000000000..4ccc872d4c --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_app_main.c @@ -0,0 +1,52 @@ +/* + * SPDX-FileCopyrightText: 2015-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "unity.h" +#include "unity_test_runner.h" +#include "esp_heap_caps.h" + +// Some resources are lazy allocated in RMT driver, so we reserved this threadhold when checking memory leak +// A better way to check a potential memory leak is running a same case by twice, for the second time, the memory usage delta should be zero +#define TEST_MEMORY_LEAK_THRESHOLD (-300) + +static size_t before_free_8bit; +static size_t before_free_32bit; + +static void check_leak(size_t before_free, size_t after_free, const char *type) +{ + ssize_t delta = after_free - before_free; + printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta); + TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak"); +} + +void setUp(void) +{ + before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); +} + +void tearDown(void) +{ + size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT); + size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT); + check_leak(before_free_8bit, after_free_8bit, "8BIT"); + check_leak(before_free_32bit, after_free_32bit, "32BIT"); +} + +void app_main(void) +{ + // ____ __ __ _____ _____ _ + // | _ \| \/ |_ _| |_ _|__ ___| |_ + // | |_) | |\/| | | | | |/ _ \/ __| __| + // | _ <| | | | | | | | __/\__ \ |_ + // |_| \_\_| |_| |_| |_|\___||___/\__| + printf(" ____ __ __ _____ _____ _\r\n"); + printf("| _ \\| \\/ |_ _| |_ _|__ ___| |_\r\n"); + printf("| |_) | |\\/| | | | | |/ _ \\/ __| __|\r\n"); + printf("| _ <| | | | | | | | __/\\__ \\ |_\r\n"); + printf("|_| \\_\\_| |_| |_| |_|\\___||___/\\__|\r\n"); + unity_run_menu(); +} diff --git a/components/driver/test_apps/rmt/main/test_rmt_common.c b/components/driver/test_apps/rmt/main/test_rmt_common.c new file mode 100644 index 0000000000..1b6dedb866 --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_rmt_common.c @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_rx.h" +#include "soc/soc_caps.h" + +TEST_CASE("rmt_channel_install_uninstall", "[rmt]") +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .gpio_num = 0, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, + .trans_queue_depth = 1, + }; + rmt_channel_handle_t tx_channels[SOC_RMT_TX_CANDIDATES_PER_GROUP] = {}; + rmt_rx_channel_config_t rx_channel_cfg = { + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .gpio_num = 2, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, + }; + rmt_channel_handle_t rx_channels[SOC_RMT_RX_CANDIDATES_PER_GROUP] = {}; + + printf("install tx/rx channels, each channel takes one memory block\r\n"); + for (int i = 0; i < SOC_RMT_TX_CANDIDATES_PER_GROUP; i++) { + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[i])); + } + // alloc more when tx channels are exhausted should report error + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[0])); + for (int i = 0; i < SOC_RMT_TX_CANDIDATES_PER_GROUP; i++) { + TEST_ESP_OK(rmt_del_channel(tx_channels[i])); + } + for (int i = 0; i < SOC_RMT_RX_CANDIDATES_PER_GROUP; i++) { + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[i])); + } + // alloc more when rx channels are exhausted should report error + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[0])); + for (int i = 0; i < SOC_RMT_RX_CANDIDATES_PER_GROUP; i++) { + TEST_ESP_OK(rmt_del_channel(rx_channels[i])); + } + + printf("install tx/rx channels, each channel takes two memory blocks\r\n"); + tx_channel_cfg.mem_block_symbols = 2 * SOC_RMT_MEM_WORDS_PER_CHANNEL; + rx_channel_cfg.mem_block_symbols = 2 * SOC_RMT_MEM_WORDS_PER_CHANNEL; + for (int i = 0; i < SOC_RMT_TX_CANDIDATES_PER_GROUP / 2; i++) { + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[i])); + } + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[0])); + for (int i = 0; i < SOC_RMT_TX_CANDIDATES_PER_GROUP / 2; i++) { + TEST_ESP_OK(rmt_del_channel(tx_channels[i])); + } + for (int i = 0; i < SOC_RMT_RX_CANDIDATES_PER_GROUP / 2; i++) { + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[i])); + } + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[0])); + for (int i = 0; i < SOC_RMT_RX_CANDIDATES_PER_GROUP / 2; i++) { + TEST_ESP_OK(rmt_del_channel(rx_channels[i])); + } + + printf("install tx+rx channels, memory blocks exhaustive\r\n"); + tx_channel_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[0])); + tx_channel_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL * (SOC_RMT_CHANNELS_PER_GROUP - 2); + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[1])); + rx_channel_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[0])); + TEST_ESP_ERR(ESP_ERR_NOT_FOUND, rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[1])); + TEST_ESP_OK(rmt_del_channel(tx_channels[0])); + TEST_ESP_OK(rmt_del_channel(tx_channels[1])); + TEST_ESP_OK(rmt_del_channel(rx_channels[0])); + +#if SOC_RMT_SUPPORT_DMA + printf("install DMA channel + normal channel\r\n"); + tx_channel_cfg.mem_block_symbols = 4096; // DMA is aimed for transfer large amount of buffers + tx_channel_cfg.flags.with_dma = true; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[0])); + rx_channel_cfg.flags.with_dma = true; + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[0])); + tx_channel_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; + tx_channel_cfg.flags.with_dma = false; + rx_channel_cfg.mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL; + rx_channel_cfg.flags.with_dma = false; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[1])); + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channels[1])); + for (int i = 0; i < 2; i++) { + TEST_ESP_OK(rmt_del_channel(tx_channels[i])); + TEST_ESP_OK(rmt_del_channel(rx_channels[i])); + } +#endif // SOC_RMT_SUPPORT_DMA +} diff --git a/components/driver/test_apps/rmt/main/test_rmt_rx.c b/components/driver/test_apps/rmt/main/test_rmt_rx.c new file mode 100644 index 0000000000..7528843fe7 --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_rmt_rx.c @@ -0,0 +1,202 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_rx.h" +#include "soc/soc_caps.h" +#include "test_util_rmt_encoders.h" + +#if CONFIG_RMT_ISR_IRAM_SAFE +#define TEST_RMT_CALLBACK_ATTR IRAM_ATTR +#else +#define TEST_RMT_CALLBACK_ATTR +#endif + +typedef struct { + TaskHandle_t task_to_notify; + size_t received_symbol_num; +} test_nec_rx_user_data_t; + +TEST_RMT_CALLBACK_ATTR +static bool test_rmt_rx_done_callback(rmt_channel_handle_t channel, rmt_rx_done_event_data_t *edata, void *user_data) +{ + BaseType_t high_task_wakeup = pdFALSE; + test_nec_rx_user_data_t *test_user_data = (test_nec_rx_user_data_t *)user_data; + rmt_symbol_word_t *remote_codes = edata->received_symbols; + esp_rom_printf("%u symbols decoded:\r\n", edata->num_symbols); + for (size_t i = 0; i < edata->num_symbols; i++) { + esp_rom_printf("{%d:%d},{%d:%d}\r\n", remote_codes[i].level0, remote_codes[i].duration0, remote_codes[i].level1, remote_codes[i].duration1); + } + vTaskNotifyGiveFromISR(test_user_data->task_to_notify, &high_task_wakeup); + test_user_data->received_symbol_num = edata->num_symbols; + return high_task_wakeup == pdTRUE; +} + +static void test_rmt_rx_nec_carrier(size_t mem_block_symbols, bool with_dma, rmt_clock_source_t clk_src) +{ + rmt_rx_channel_config_t rx_channel_cfg = { + .clk_src = clk_src, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + .mem_block_symbols = mem_block_symbols, + .gpio_num = 0, + .flags.with_dma = with_dma, + .flags.io_loop_back = true, // the GPIO will act like a loopback + }; + printf("install rx channel\r\n"); + rmt_channel_handle_t rx_channel = NULL; + TEST_ESP_OK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channel)); + printf("register rx event callbacks\r\n"); + rmt_rx_event_callbacks_t cbs = { + .on_recv_done = test_rmt_rx_done_callback, + }; + test_nec_rx_user_data_t test_user_data = { + .task_to_notify = xTaskGetCurrentTaskHandle(), + }; + TEST_ESP_OK(rmt_rx_register_event_callbacks(rx_channel, &cbs, &test_user_data)); + + rmt_tx_channel_config_t tx_channel_cfg = { + .clk_src = clk_src, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .trans_queue_depth = 4, + .gpio_num = 0, + .flags.io_loop_back = true, // TX channel and RX channel will bounded to the same GPIO + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel)); + + printf("install nec protocol encoder\r\n"); + rmt_encoder_handle_t nec_encoder = NULL; + TEST_ESP_OK(test_rmt_new_nec_protocol_encoder(&nec_encoder)); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel)); + printf("enable rx channel\r\n"); + TEST_ESP_OK(rmt_enable(rx_channel)); + + rmt_symbol_word_t remote_codes[128]; + + rmt_receive_config_t receive_config = { + .signal_range_min_ns = 1250, + .signal_range_max_ns = 12000000, + }; + + // ready to receive + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send NEC frame without carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 34); + + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send NEC frame without carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 34); + +#if SOC_RMT_SUPPORT_RX_PINGPONG + // ready to receive + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send customized NEC frame without carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0xFF00, 0xFF00, 0xFF00, 0xFF00 + }, 8, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 66); +#else + // ready to receive + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send customized NEC frame without carrier\r\n"); + // the maximum symbols can receive is its memory block capacity + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0xFF00, 0xFF00, 0xFF00, 0xFF00, 0xFF00 + }, 10, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, mem_block_symbols); +#endif // SOC_RMT_SUPPORT_RX_PINGPONG + +#if SOC_RMT_SUPPORT_RX_DEMODULATION + rmt_carrier_config_t carrier_cfg = { + .duty_cycle = 0.33, + .frequency_hz = 38000, + }; + printf("enable modulation for tx channel\r\n"); + TEST_ESP_OK(rmt_apply_carrier(tx_channel, &carrier_cfg)); + printf("enable demodulation for rx channel\r\n"); + // need to leave a tolerance for the carrier demodulation, can't set the carrier frequency exactly to 38KHz + // should reduce frequency to some extend + carrier_cfg.frequency_hz = 25000; + TEST_ESP_OK(rmt_apply_carrier(rx_channel, &carrier_cfg)); + + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send NEC frame with carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 34); + +#if SOC_RMT_SUPPORT_RX_PINGPONG + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send customized frame with carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0xFF00, 0xFF00, 0xFF00, 0xFF00 + }, 8, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 66); +#endif // SOC_RMT_SUPPORT_RX_PINGPONG + + printf("disable modulation and demodulation for tx and rx channels\r\n"); + TEST_ESP_OK(rmt_apply_carrier(tx_channel, NULL)); + TEST_ESP_OK(rmt_apply_carrier(rx_channel, NULL)); +#endif // SOC_RMT_SUPPORT_RX_DEMODULATION + + TEST_ESP_OK(rmt_receive(rx_channel, remote_codes, sizeof(remote_codes), &receive_config)); + printf("send NEC frame without carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ASSERT_NOT_EQUAL(0, ulTaskNotifyTake(pdFALSE, pdMS_TO_TICKS(1000))); + TEST_ASSERT_EQUAL(test_user_data.received_symbol_num, 34); + + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + printf("disable tx and rx channels\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel)); + TEST_ESP_OK(rmt_disable(rx_channel)); + printf("delete channels and encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(rx_channel)); + TEST_ESP_OK(rmt_del_channel(tx_channel)); + TEST_ESP_OK(rmt_del_encoder(nec_encoder)); +} + +TEST_CASE("rmt_rx_nec_carrier_no_dma", "[rmt]") +{ + // test width different clock sources + rmt_clock_source_t clk_srcs[] = SOC_RMT_CLKS; + for (size_t i = 0; i < sizeof(clk_srcs) / sizeof(clk_srcs[0]); i++) { + test_rmt_rx_nec_carrier(SOC_RMT_MEM_WORDS_PER_CHANNEL, false, clk_srcs[i]); + } +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_rx_nec_carrier_with_dma", "[rmt]") +{ + test_rmt_rx_nec_carrier(128, true, RMT_CLK_SRC_DEFAULT); +} +#endif diff --git a/components/driver/test_apps/rmt/main/test_rmt_tx.c b/components/driver/test_apps/rmt/main/test_rmt_tx.c new file mode 100644 index 0000000000..676834f42b --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_rmt_tx.c @@ -0,0 +1,607 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include "sdkconfig.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "unity.h" +#include "driver/rmt_tx.h" +#include "esp_timer.h" +#include "soc/soc_caps.h" +#include "test_util_rmt_encoders.h" + +#if CONFIG_RMT_ISR_IRAM_SAFE +#define TEST_RMT_CALLBACK_ATTR IRAM_ATTR +#else +#define TEST_RMT_CALLBACK_ATTR +#endif + +static void test_rmt_channel_single_trans(size_t mem_block_symbols, bool with_dma) +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = mem_block_symbols, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz, 1 tick = 0.1us (led strip needs a high resolution) + .trans_queue_depth = 4, + .gpio_num = 0, + .flags.with_dma = with_dma, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel_single_led = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel_single_led)); + printf("install led strip encoder\r\n"); + rmt_encoder_handle_t led_strip_encoder = NULL; + TEST_ESP_OK(test_rmt_new_led_strip_encoder(&led_strip_encoder)); + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel_single_led)); + + printf("single transmission: light up one RGB LED\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + TEST_ESP_OK(rmt_transmit(tx_channel_single_led, led_strip_encoder, (uint8_t[]) { + 0x00, 0x7F, 0xFF + }, 3, &transmit_config)); + // adding extra delay here for visualizing + vTaskDelay(pdMS_TO_TICKS(500)); + TEST_ESP_OK(rmt_transmit(tx_channel_single_led, led_strip_encoder, (uint8_t[]) { + 0xFF, 0x00, 0x7F + }, 3, &transmit_config)); + vTaskDelay(pdMS_TO_TICKS(500)); + TEST_ESP_OK(rmt_transmit(tx_channel_single_led, led_strip_encoder, (uint8_t[]) { + 0x7F, 0xFF, 0x00 + }, 3, &transmit_config)); + vTaskDelay(pdMS_TO_TICKS(500)); + + // can't delete channel if it's not in stop state + TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, rmt_del_channel(tx_channel_single_led)); + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel_single_led)); + printf("remove tx channel and led strip encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel_single_led)); + TEST_ESP_OK(rmt_del_encoder(led_strip_encoder)); +} + +TEST_CASE("rmt_single_trans_no_dma", "[rmt]") +{ + test_rmt_channel_single_trans(SOC_RMT_MEM_WORDS_PER_CHANNEL, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_single_trans_with_dma", "[rmt]") +{ + test_rmt_channel_single_trans(512, true); +} +#endif + +static void test_rmt_ping_pong_trans(size_t mem_block_symbols, bool with_dma) +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = mem_block_symbols, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz, 1 tick = 0.1us (led strip needs a high resolution) + .trans_queue_depth = 4, + .gpio_num = 0, + .flags.with_dma = with_dma, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel_multi_leds = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel_multi_leds)); + printf("install led strip encoder\r\n"); + rmt_encoder_handle_t led_strip_encoder = NULL; + TEST_ESP_OK(test_rmt_new_led_strip_encoder(&led_strip_encoder)); + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel_multi_leds)); + + // Mutiple LEDs (ping-pong in the background) + printf("ping pong transmission: light up 100 RGB LEDs\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; +#define TEST_LED_NUM 100 + uint8_t leds_grb[TEST_LED_NUM * 3] = {}; + // color: Material Design Green-A200 (#69F0AE) + for (int i = 0; i < TEST_LED_NUM * 3; i += 3) { + leds_grb[i + 0] = 0xF0; + leds_grb[i + 1] = 0x69; + leds_grb[i + 2] = 0xAE; + } + printf("start transmission and stop immediately, only a few LEDs are light up\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, TEST_LED_NUM * 3, &transmit_config)); + // this second transmission will stay in the queue and shouldn't be dispatched until we restart the tx channel later + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, TEST_LED_NUM * 3, &transmit_config)); + esp_rom_delay_us(100); + TEST_ESP_OK(rmt_disable(tx_channel_multi_leds)); + vTaskDelay(pdTICKS_TO_MS(500)); + + printf("enable tx channel again\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel_multi_leds)); + // adding extra delay here for visualizing + vTaskDelay(pdTICKS_TO_MS(500)); + // color: Material Design Pink-A200 (#FF4081) + for (int i = 0; i < TEST_LED_NUM * 3; i += 3) { + leds_grb[i + 0] = 0x40; + leds_grb[i + 1] = 0xFF; + leds_grb[i + 2] = 0x81; + } + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, TEST_LED_NUM * 3, &transmit_config)); + vTaskDelay(pdTICKS_TO_MS(500)); + // color: Material Design Orange-900 (#E65100) + for (int i = 0; i < TEST_LED_NUM * 3; i += 3) { + leds_grb[i + 0] = 0x51; + leds_grb[i + 1] = 0xE6; + leds_grb[i + 2] = 0x00; + } + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, TEST_LED_NUM * 3, &transmit_config)); + vTaskDelay(pdTICKS_TO_MS(500)); + + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel_multi_leds)); + printf("remove tx channel and led strip encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel_multi_leds)); + TEST_ESP_OK(rmt_del_encoder(led_strip_encoder)); +#undef TEST_LED_NUM +} + +TEST_CASE("rmt_ping_pong_trans_no_dma", "[rmt]") +{ + test_rmt_ping_pong_trans(SOC_RMT_MEM_WORDS_PER_CHANNEL, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_ping_pong_trans_with_dma", "[rmt]") +{ + test_rmt_ping_pong_trans(1024, true); +} +#endif + +TEST_RMT_CALLBACK_ATTR +static bool test_rmt_tx_done_cb_check_event_data(rmt_channel_handle_t channel, rmt_tx_done_event_data_t *edata, void *user_data) +{ + uint32_t *p_expected_encoded_size = (uint32_t *)user_data; + TEST_ASSERT_EQUAL(*p_expected_encoded_size, edata->num_symbols); + return false; +} + +static void test_rmt_trans_done_event(size_t mem_block_symbols, bool with_dma) +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = mem_block_symbols, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz, 1 tick = 0.1us (led strip needs a high resolution) + .trans_queue_depth = 1, + .gpio_num = 0, + .flags.with_dma = with_dma, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel_multi_leds = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel_multi_leds)); + printf("install led strip encoder\r\n"); + rmt_encoder_handle_t led_strip_encoder = NULL; + TEST_ESP_OK(test_rmt_new_led_strip_encoder(&led_strip_encoder)); + + printf("register trans done event callback\r\n"); + rmt_tx_event_callbacks_t cbs = { + .on_trans_done = test_rmt_tx_done_cb_check_event_data, + }; + uint32_t expected_encoded_size = 0; + TEST_ESP_OK(rmt_tx_register_event_callbacks(tx_channel_multi_leds, &cbs, &expected_encoded_size)); + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel_multi_leds)); + + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + + printf("transmit dynamic number of LEDs\r\n"); +#define TEST_LED_NUM 40 + uint8_t leds_grb[TEST_LED_NUM * 3] = {}; + // color: Material Design Purple-800 (6A1B9A) + for (int i = 0; i < TEST_LED_NUM * 3; i += 3) { + leds_grb[i + 0] = 0x1B; + leds_grb[i + 1] = 0x6A; + leds_grb[i + 2] = 0x9A; + } + for (int i = 1; i <= TEST_LED_NUM; i++) { + expected_encoded_size = 2 + i * 24; // 2 = 1 reset symbol + 1 eof symbol, 24 = 8*3(RGB) + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, i * 3, &transmit_config)); + // wait for the transmission finished and recycled + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel_multi_leds, -1)); + } + + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel_multi_leds)); + printf("remove tx channel and led strip encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel_multi_leds)); + TEST_ESP_OK(rmt_del_encoder(led_strip_encoder)); +#undef TEST_LED_NUM +} + +TEST_CASE("rmt_trans_done_event_callback_no_dma", "[rmt]") +{ + test_rmt_trans_done_event(SOC_RMT_MEM_WORDS_PER_CHANNEL, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_trans_done_event_callback_with_dma", "[rmt]") +{ + test_rmt_trans_done_event(332, true); +} +#endif + +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + +TEST_RMT_CALLBACK_ATTR +static bool test_rmt_loop_done_cb_check_event_data(rmt_channel_handle_t channel, rmt_tx_done_event_data_t *edata, void *user_data) +{ + uint32_t *p_expected_encoded_size = (uint32_t *)user_data; + TEST_ASSERT_EQUAL(*p_expected_encoded_size, edata->num_symbols); + return false; +} + +static void test_rmt_loop_trans(size_t mem_block_symbols, bool with_dma) +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .mem_block_symbols = mem_block_symbols, + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz, 1 tick = 0.1us (led strip needs a high resolution) + .trans_queue_depth = 4, + .gpio_num = 0, + .flags.with_dma = with_dma, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel_multi_leds = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel_multi_leds)); + printf("install led strip encoder\r\n"); + rmt_encoder_handle_t led_strip_encoder = NULL; + TEST_ESP_OK(test_rmt_new_led_strip_encoder(&led_strip_encoder)); + + printf("register loop done event callback\r\n"); + rmt_tx_event_callbacks_t cbs = { + .on_trans_done = test_rmt_loop_done_cb_check_event_data, + }; + uint32_t expected_encoded_size = 0; + TEST_ESP_OK(rmt_tx_register_event_callbacks(tx_channel_multi_leds, &cbs, &expected_encoded_size)); + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel_multi_leds)); + + printf("loop transmission: light up RGB LEDs in a loop\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 5, + }; +#define TEST_LED_NUM 3 + uint8_t leds_grb[TEST_LED_NUM * 3] = {}; + for (int i = 0; i < TEST_LED_NUM * 3; i++) { + leds_grb[i] = 0x10 + i; + } + expected_encoded_size = 2 + 24 * TEST_LED_NUM; + TEST_ESP_OK(rmt_transmit(tx_channel_multi_leds, led_strip_encoder, leds_grb, TEST_LED_NUM * 3, &transmit_config)); + vTaskDelay(pdTICKS_TO_MS(100)); + + printf("wait for loop transactions done\r\n"); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel_multi_leds, -1)); + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel_multi_leds)); + printf("remove tx channel and led strip encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel_multi_leds)); + TEST_ESP_OK(rmt_del_encoder(led_strip_encoder)); +#undef TEST_LED_NUM +} + +TEST_CASE("rmt_loop_trans_no_dma", "[rmt]") +{ + test_rmt_loop_trans(SOC_RMT_MEM_WORDS_PER_CHANNEL * 2, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_loop_trans_with_dma", "[rmt]") +{ + test_rmt_loop_trans(128, true); +} +#endif // SOC_RMT_SUPPORT_DMA +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + +TEST_CASE("rmt_infinite_loop_trans", "[rmt]") +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL, + .gpio_num = 2, + .trans_queue_depth = 3, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel)); + printf("install step motor encoder\r\n"); + // stepper encoder is as simple as a copy encoder + rmt_encoder_t *copy_encoder = NULL; + rmt_copy_encoder_config_t copy_encoder_config = {}; + TEST_ESP_OK(rmt_new_copy_encoder(©_encoder_config, ©_encoder)); + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel)); + + rmt_transmit_config_t transmit_config = { + .loop_count = -1, // infinite loop transmission + }; + + printf("infinite loop transmission: keep spinning stepper motor\r\n"); + uint32_t step_motor_frequency_hz = 1000; // 1KHz + uint32_t rmt_raw_symbol_duration = 1000000 / step_motor_frequency_hz / 2; + // 1KHz PWM, Period: 1ms + rmt_symbol_word_t stepper_motor_rmt_symbol = { + .level0 = 0, + .duration0 = rmt_raw_symbol_duration, + .level1 = 1, + .duration1 = rmt_raw_symbol_duration, + }; + TEST_ESP_OK(rmt_transmit(tx_channel, copy_encoder, &stepper_motor_rmt_symbol, sizeof(stepper_motor_rmt_symbol), &transmit_config)); + // not trans done event should be triggered + TEST_ESP_ERR(ESP_ERR_TIMEOUT, rmt_tx_wait_all_done(tx_channel, 500)); + + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel)); + // the flush operation should return immediately, as there's not pending transactions and the TX machine has stopped + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, 0)); + +#if SOC_RMT_SUPPORT_TX_LOOP_COUNT + printf("enable tx channel again\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel)); + + printf("finite loop transmission: spinning stepper motor with various number of loops\r\n"); +#define TEST_RMT_LOOPS 5 + uint32_t pwm_freq[TEST_RMT_LOOPS] = {}; + rmt_symbol_word_t pwm_rmt_symbols[TEST_RMT_LOOPS] = {}; + for (int i = 0; i < TEST_RMT_LOOPS; i++) { + transmit_config.loop_count = 100 * i; + pwm_freq[i] = 1000 * (i + 1); + uint32_t pwm_symbol_duration = 1000000 / pwm_freq[i] / 2; + // 1KHz PWM, Period: 1ms + pwm_rmt_symbols[i] = (rmt_symbol_word_t) { + .level0 = 0, + .duration0 = pwm_symbol_duration, + .level1 = 1, + .duration1 = pwm_symbol_duration, + }; + TEST_ESP_OK(rmt_transmit(tx_channel, copy_encoder, &pwm_rmt_symbols[i], sizeof(rmt_symbol_word_t), &transmit_config)); + } + + printf("wait for loop transactions done\r\n"); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); // wait forever + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel)); +#undef TEST_RMT_LOOPS +#endif // SOC_RMT_SUPPORT_TX_LOOP_COUNT + + printf("remove tx channel and motor encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel)); + TEST_ESP_OK(rmt_del_encoder(copy_encoder)); +} + +static void test_rmt_tx_nec_carrier(size_t mem_block_symbols, bool with_dma) +{ + rmt_tx_channel_config_t tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 1000000, // 1MHz, 1 tick = 1us + .mem_block_symbols = mem_block_symbols, + .gpio_num = 2, + .trans_queue_depth = 4, + .flags.with_dma = with_dma, + }; + printf("install tx channel\r\n"); + rmt_channel_handle_t tx_channel = NULL; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel)); + printf("install nec protocol encoder\r\n"); + rmt_encoder_handle_t nec_encoder = NULL; + TEST_ESP_OK(test_rmt_new_nec_protocol_encoder(&nec_encoder)); + + printf("enable tx channel\r\n"); + TEST_ESP_OK(rmt_enable(tx_channel)); + + printf("transmit nec frame without carrier\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + printf("transmit nec frame with carrier\r\n"); + rmt_carrier_config_t carrier_cfg = { + .duty_cycle = 0.33, + .frequency_hz = 38000, + }; + TEST_ESP_OK(rmt_apply_carrier(tx_channel, &carrier_cfg)); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + printf("remove carrier\r\n"); + TEST_ESP_OK(rmt_apply_carrier(tx_channel, NULL)); + + printf("transmit nec frame without carrier\r\n"); + TEST_ESP_OK(rmt_transmit(tx_channel, nec_encoder, (uint16_t[]) { + 0x0440, 0x3003 // address, command + }, 4, &transmit_config)); + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channel, -1)); + + printf("disable tx channel\r\n"); + TEST_ESP_OK(rmt_disable(tx_channel)); + printf("remove tx channel and nec encoder\r\n"); + TEST_ESP_OK(rmt_del_channel(tx_channel)); + TEST_ESP_OK(rmt_del_encoder(nec_encoder)); +} + +TEST_CASE("rmt_tx_nec_carrier_no_dma", "[rmt]") +{ + test_rmt_tx_nec_carrier(SOC_RMT_MEM_WORDS_PER_CHANNEL, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_tx_nec_carrier_with_dma", "[rmt]") +{ + test_rmt_tx_nec_carrier(128, true); +} +#endif + +TEST_RMT_CALLBACK_ATTR +static bool test_rmt_tx_done_cb_record_time(rmt_channel_handle_t channel, rmt_tx_done_event_data_t *edata, void *user_data) +{ + int64_t *record_time = (int64_t *)user_data; + *record_time = esp_timer_get_time(); + return false; +} + +static void test_rmt_multi_channels_trans(size_t channel0_mem_block_symbols, size_t channel1_mem_block_symbols, bool channel0_with_dma, bool channel1_with_dma) +{ +#define TEST_RMT_CHANS 2 +#define TEST_LED_NUM 24 + rmt_tx_channel_config_t tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = 10000000, // 10MHz, 1 tick = 0.1us (led strip needs a high resolution) + .trans_queue_depth = 4, + }; + printf("install tx channels\r\n"); + rmt_channel_handle_t tx_channels[TEST_RMT_CHANS] = {NULL}; + int gpio_nums[TEST_RMT_CHANS] = {0, 2}; + size_t mem_blk_syms[TEST_RMT_CHANS] = {channel0_mem_block_symbols, channel1_mem_block_symbols}; + bool dma_flags[TEST_RMT_CHANS] = {channel0_with_dma, channel1_with_dma}; + for (int i = 0; i < TEST_RMT_CHANS; i++) { + tx_channel_cfg.gpio_num = gpio_nums[i]; + tx_channel_cfg.mem_block_symbols = mem_blk_syms[i]; + tx_channel_cfg.flags.with_dma = dma_flags[i]; + TEST_ESP_OK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channels[i])); + } + + printf("install led strip encoders\r\n"); + rmt_encoder_handle_t led_strip_encoders[TEST_RMT_CHANS] = {NULL}; + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(test_rmt_new_led_strip_encoder(&led_strip_encoders[i])); + } + + printf("register tx event callback\r\n"); + rmt_tx_event_callbacks_t cbs = { + .on_trans_done = test_rmt_tx_done_cb_record_time + }; + int64_t record_stop_time[TEST_RMT_CHANS] = {}; + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_tx_register_event_callbacks(tx_channels[i], &cbs, &record_stop_time[i])); + } + + printf("enable tx channels\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_enable(tx_channels[i])); + } + + uint8_t leds_grb[TEST_LED_NUM * 3] = {}; + // color: Material Design Green-A200 (#69F0AE) + for (int i = 0; i < TEST_LED_NUM * 3; i += 3) { + leds_grb[i + 0] = 0xF0; + leds_grb[i + 1] = 0x69; + leds_grb[i + 2] = 0xAE; + } + + printf("transmit without synchronization\r\n"); + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + // the channels should work independently, without synchronization + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_transmit(tx_channels[i], led_strip_encoders[i], leds_grb, TEST_LED_NUM * 3, &transmit_config)); + } + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channels[i], -1)); + } + printf("stop time (no sync):\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + printf("\t%lld\r\n", record_stop_time[i]); + } + // without synchronization, there will be obvious time shift + TEST_ASSERT((record_stop_time[1] - record_stop_time[0]) < 100); + + printf("install sync manager\r\n"); + rmt_sync_manager_handle_t synchro = NULL; + rmt_sync_manager_config_t synchro_config = { + .tx_channel_array = tx_channels, + .array_size = TEST_RMT_CHANS, + }; +#if SOC_RMT_SUPPORT_TX_SYNCHRO + TEST_ESP_OK(rmt_new_sync_manager(&synchro_config, &synchro)); +#else + TEST_ASSERT_EQUAL(ESP_ERR_NOT_SUPPORTED, rmt_new_sync_manager(&synchro_config, &synchro)); +#endif // SOC_RMT_SUPPORT_TX_SYNCHRO + +#if SOC_RMT_SUPPORT_TX_SYNCHRO + printf("transmit with synchronization\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_transmit(tx_channels[i], led_strip_encoders[i], leds_grb, TEST_LED_NUM * 3, &transmit_config)); + // manually introduce the delay, to show the managed channels are indeed in sync + vTaskDelay(pdMS_TO_TICKS(10)); + } + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channels[i], -1)); + } + printf("stop time (with sync):\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + printf("\t%lld\r\n", record_stop_time[i]); + } + // because of synchronization, the managed channels will stop at the same time + // but call of `esp_timer_get_time` won't happen at the same time, so there still be time drift, very small + TEST_ASSERT((record_stop_time[1] - record_stop_time[0]) < 10); + + printf("reset sync manager\r\n"); + TEST_ESP_OK(rmt_sync_reset(synchro)); + printf("transmit with synchronization again\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_transmit(tx_channels[i], led_strip_encoders[i], leds_grb, TEST_LED_NUM * 3, &transmit_config)); + // manually introduce the delay, ensure the channels get synchronization + vTaskDelay(pdMS_TO_TICKS(10)); + } + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_tx_wait_all_done(tx_channels[i], -1)); + } + printf("stop time (with sync):\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + printf("\t%lld\r\n", record_stop_time[i]); + } + TEST_ASSERT((record_stop_time[1] - record_stop_time[0]) < 10); + + printf("delete sync manager\r\n"); + TEST_ESP_OK(rmt_del_sync_manager(synchro)); +#endif // SOC_RMT_SUPPORT_TX_SYNCHRO + + printf("disable tx channels\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_disable(tx_channels[i])); + } + printf("delete channels and encoders\r\n"); + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_del_channel(tx_channels[i])); + } + for (int i = 0; i < TEST_RMT_CHANS; i++) { + TEST_ESP_OK(rmt_del_encoder(led_strip_encoders[i])); + } +#undef TEST_LED_NUM +#undef TEST_RMT_CHANS +} + +TEST_CASE("rmt_multi_channels_trans_no_dma", "[rmt]") +{ + test_rmt_multi_channels_trans(SOC_RMT_MEM_WORDS_PER_CHANNEL, SOC_RMT_MEM_WORDS_PER_CHANNEL, false, false); +} + +#if SOC_RMT_SUPPORT_DMA +TEST_CASE("rmt_multi_channels_trans_with_dma", "[rmt]") +{ + test_rmt_multi_channels_trans(1024, SOC_RMT_MEM_WORDS_PER_CHANNEL, true, false); +} +#endif diff --git a/components/driver/test_apps/rmt/main/test_util_rmt_encoders.c b/components/driver/test_apps/rmt/main/test_util_rmt_encoders.c new file mode 100644 index 0000000000..e99bad5d53 --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_util_rmt_encoders.c @@ -0,0 +1,214 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "unity.h" +#include "driver/rmt_encoder.h" + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *copy_encoder; + int state; + rmt_symbol_word_t reset_code; +} rmt_led_strip_encoder_t; + +static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encode_state_t session_state = 0; + rmt_encode_state_t state = 0; + size_t encoded_symbols = 0; + switch (led_encoder->state) { + case 0: + encoded_symbols += led_encoder->bytes_encoder->encode(led_encoder->bytes_encoder, channel, primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 1; // switch to next state when current encoding session finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + // fall-through + case 1: + encoded_symbols += led_encoder->copy_encoder->encode(led_encoder->copy_encoder, channel, &led_encoder->reset_code, sizeof(led_encoder->reset_code), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + state |= RMT_ENCODING_COMPLETE; + led_encoder->state = 0; // back to the initial encoding session + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_del_encoder(led_encoder->bytes_encoder); + rmt_del_encoder(led_encoder->copy_encoder); + free(led_encoder); + return ESP_OK; +} + +static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_reset(led_encoder->bytes_encoder); + rmt_encoder_reset(led_encoder->copy_encoder); + led_encoder->state = 0; + return ESP_OK; +} + +esp_err_t test_rmt_new_led_strip_encoder(rmt_encoder_handle_t *ret_encoder) +{ + rmt_led_strip_encoder_t *led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t)); + led_encoder->base.encode = rmt_encode_led_strip; + led_encoder->base.del = rmt_del_led_strip_encoder; + led_encoder->base.reset = rmt_led_strip_encoder_reset; + // different led strip might have its own timing requirements, following parameter is for WS2812 + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 3, // T0H=0.3us + .level1 = 0, + .duration1 = 9, // T0L=0.9us + }, + .bit1 = { + .level0 = 1, + .duration0 = 9, // T1H=0.9us + .level1 = 0, + .duration1 = 3, // T1L=0.3us + }, + .flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0 + }; + TEST_ESP_OK(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder)); + rmt_copy_encoder_config_t copy_encoder_config = {}; + TEST_ESP_OK(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder)); + led_encoder->reset_code = (rmt_symbol_word_t) { + .level0 = 0, + .duration0 = 250, + .level1 = 0, + .duration1 = 250, + }; // reset duration defaults to 50us + *ret_encoder = &led_encoder->base; + return ESP_OK; +} + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *copy_encoder; + rmt_encoder_t *bytes_encoder; + int state; +} rmt_nec_protocol_encoder_t; + +static size_t rmt_encode_nec_protocol(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_nec_protocol_encoder_t *nec_encoder = __containerof(encoder, rmt_nec_protocol_encoder_t, base); + rmt_encode_state_t session_state = 0; + rmt_encode_state_t state = 0; + size_t encoded_symbols = 0; + const rmt_symbol_word_t nec_leading_symbol = { + .level0 = 1, + .duration0 = 9000, + .level1 = 0, + .duration1 = 4500 + }; + const rmt_symbol_word_t nec_ending_symbol = { + .level0 = 1, + .duration0 = 560, + .level1 = 0, + .duration1 = 0x7FFF + }; + switch (nec_encoder->state) { + case 0: + encoded_symbols += nec_encoder->copy_encoder->encode(nec_encoder->copy_encoder, channel, &nec_leading_symbol, sizeof(nec_leading_symbol), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 1; // we can only switch to next state when current encoder finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + // fall-through + case 1: + encoded_symbols += nec_encoder->bytes_encoder->encode(nec_encoder->bytes_encoder, channel, primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 2; // we can only switch to next state when current encoder finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + // fall-through + case 2: + encoded_symbols += nec_encoder->copy_encoder->encode(nec_encoder->copy_encoder, channel, &nec_ending_symbol, sizeof(nec_ending_symbol), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + state |= RMT_ENCODING_COMPLETE; + nec_encoder->state = 0; // back to the initial encoding session + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_nec_protocol_encoder(rmt_encoder_t *encoder) +{ + rmt_nec_protocol_encoder_t *nec_encoder = __containerof(encoder, rmt_nec_protocol_encoder_t, base); + rmt_del_encoder(nec_encoder->copy_encoder); + rmt_del_encoder(nec_encoder->bytes_encoder); + free(nec_encoder); + return ESP_OK; +} + +static esp_err_t rmt_nec_protocol_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_nec_protocol_encoder_t *nec_encoder = __containerof(encoder, rmt_nec_protocol_encoder_t, base); + rmt_encoder_reset(nec_encoder->copy_encoder); + rmt_encoder_reset(nec_encoder->bytes_encoder); + nec_encoder->state = 0; + return ESP_OK; +} + +esp_err_t test_rmt_new_nec_protocol_encoder(rmt_encoder_handle_t *ret_encoder) +{ + rmt_nec_protocol_encoder_t *nec_encoder = calloc(1, sizeof(rmt_nec_protocol_encoder_t)); + nec_encoder->base.encode = rmt_encode_nec_protocol; + nec_encoder->base.del = rmt_del_nec_protocol_encoder; + nec_encoder->base.reset = rmt_nec_protocol_encoder_reset; + + // different IR protocol might have its own timing requirements, following parameter is for NEC + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 560, // T0H=560us + .level1 = 0, + .duration1 = 560, // T0L=560us + }, + .bit1 = { + .level0 = 1, + .duration0 = 560, // T1H=560us + .level1 = 0, + .duration1 = 1690, // T1L=1690us + }, + }; + rmt_copy_encoder_config_t copy_encoder_config = {}; + TEST_ESP_OK(rmt_new_copy_encoder(©_encoder_config, &nec_encoder->copy_encoder)); + TEST_ESP_OK(rmt_new_bytes_encoder(&bytes_encoder_config, &nec_encoder->bytes_encoder)); + + *ret_encoder = &nec_encoder->base; + return ESP_OK; +} diff --git a/components/driver/test_apps/rmt/main/test_util_rmt_encoders.h b/components/driver/test_apps/rmt/main/test_util_rmt_encoders.h new file mode 100644 index 0000000000..a325c92325 --- /dev/null +++ b/components/driver/test_apps/rmt/main/test_util_rmt_encoders.h @@ -0,0 +1,21 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +esp_err_t test_rmt_new_led_strip_encoder(rmt_encoder_handle_t *ret_encoder); + +esp_err_t test_rmt_new_nec_protocol_encoder(rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/components/driver/test_apps/rmt/pytest_rmt.py b/components/driver/test_apps/rmt/pytest_rmt.py new file mode 100644 index 0000000000..441f669120 --- /dev/null +++ b/components/driver/test_apps/rmt/pytest_rmt.py @@ -0,0 +1,24 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +@pytest.mark.parametrize( + 'config', + [ + 'iram_safe', + 'release', + ], + indirect=True, +) +def test_rmt(dut: Dut) -> None: + dut.expect('Press ENTER to see the list of tests') + dut.write('*') + dut.expect_unity_test_output() diff --git a/components/driver/test_apps/rmt/sdkconfig.ci.iram_safe b/components/driver/test_apps/rmt/sdkconfig.ci.iram_safe new file mode 100644 index 0000000000..9621665f0d --- /dev/null +++ b/components/driver/test_apps/rmt/sdkconfig.ci.iram_safe @@ -0,0 +1,7 @@ +CONFIG_COMPILER_DUMP_RTL_FILES=y +CONFIG_RMT_ISR_IRAM_SAFE=y + +# silent the error check, as the error string are stored in rodata, causing RTL check failure +CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y +CONFIG_HAL_ASSERTION_SILIENT=y diff --git a/components/driver/test_apps/rmt/sdkconfig.ci.release b/components/driver/test_apps/rmt/sdkconfig.ci.release new file mode 100644 index 0000000000..91d93f163e --- /dev/null +++ b/components/driver/test_apps/rmt/sdkconfig.ci.release @@ -0,0 +1,5 @@ +CONFIG_PM_ENABLE=y +CONFIG_FREERTOS_USE_TICKLESS_IDLE=y +CONFIG_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE=y +CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT=y diff --git a/components/driver/test_apps/rmt/sdkconfig.defaults b/components/driver/test_apps/rmt/sdkconfig.defaults new file mode 100644 index 0000000000..b308cb2ddd --- /dev/null +++ b/components/driver/test_apps/rmt/sdkconfig.defaults @@ -0,0 +1,2 @@ +CONFIG_FREERTOS_HZ=1000 +CONFIG_ESP_TASK_WDT=n diff --git a/components/hal/include/hal/rmt_hal.h b/components/hal/include/hal/rmt_hal.h index a591f90800..ef7a077857 100644 --- a/components/hal/include/hal/rmt_hal.h +++ b/components/hal/include/hal/rmt_hal.h @@ -34,6 +34,13 @@ typedef struct { */ void rmt_hal_init(rmt_hal_context_t *hal); +/** + * @brief Deinitialize the RMT HAL driver + * + * @param hal: RMT HAL context + */ +void rmt_hal_deinit(rmt_hal_context_t *hal); + /** * @brief Reset RMT TX Channel * diff --git a/components/hal/include/hal/rmt_types.h b/components/hal/include/hal/rmt_types.h index cbe689096d..f3c9a6f7ba 100644 --- a/components/hal/include/hal/rmt_types.h +++ b/components/hal/include/hal/rmt_types.h @@ -28,7 +28,7 @@ typedef union { unsigned int duration1 : 15; /*!< Duration of level1 */ unsigned int level1 : 1; /*!< Level of the second part */ }; - unsigned int val; /*!< Equivelent unsigned value for the RMT symbol */ + unsigned int val; /*!< Equivalent unsigned value for the RMT symbol */ } rmt_symbol_word_t; #ifdef __cplusplus diff --git a/components/hal/rmt_hal.c b/components/hal/rmt_hal.c index 5bff27d4ed..d83e5952da 100644 --- a/components/hal/rmt_hal.c +++ b/components/hal/rmt_hal.c @@ -10,6 +10,21 @@ void rmt_hal_init(rmt_hal_context_t *hal) { hal->regs = &RMT; + rmt_ll_power_down_mem(hal->regs, false); // turn on RMTMEM power domain + rmt_ll_enable_mem_access_nonfifo(hal->regs, true); // APB access the RMTMEM in nonfifo mode + rmt_ll_enable_interrupt(hal->regs, UINT32_MAX, false); // disable all interupt events + rmt_ll_clear_interrupt_status(hal->regs, UINT32_MAX); // clear all pending events +#if SOC_RMT_SUPPORT_TX_SYNCHRO + rmt_ll_tx_clear_sync_group(hal->regs); +#endif // SOC_RMT_SUPPORT_TX_SYNCHRO +} + +void rmt_hal_deinit(rmt_hal_context_t *hal) +{ + rmt_ll_enable_interrupt(hal->regs, UINT32_MAX, false); // disable all interupt events + rmt_ll_clear_interrupt_status(hal->regs, UINT32_MAX); // clear all pending events + rmt_ll_power_down_mem(hal->regs, true); // turn off RMTMEM power domain + hal->regs = NULL; } void rmt_hal_tx_channel_reset(rmt_hal_context_t *hal, uint32_t channel) diff --git a/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in index e3d7f2e7d0..f6c147368b 100644 --- a/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32c3/include/soc/Kconfig.soc_caps.in @@ -411,7 +411,7 @@ config SOC_RMT_SUPPORT_TX_SYNCHRO bool default y -config SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON +config SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY bool default y diff --git a/components/soc/esp32c3/include/soc/soc_caps.h b/components/soc/esp32c3/include/soc/soc_caps.h index 0ebf71f4d8..198c5015a8 100644 --- a/components/soc/esp32c3/include/soc/soc_caps.h +++ b/components/soc/esp32c3/include/soc/soc_caps.h @@ -195,7 +195,7 @@ #define SOC_RMT_SUPPORT_TX_ASYNC_STOP 1 /*!< Support stop transmission asynchronously */ #define SOC_RMT_SUPPORT_TX_LOOP_COUNT 1 /*!< Support transmit specified number of cycles in loop mode */ #define SOC_RMT_SUPPORT_TX_SYNCHRO 1 /*!< Support coordinate a group of TX channels to start simultaneously */ -#define SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON 1 /*!< TX carrier can be modulated all the time */ +#define SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY 1 /*!< TX carrier can be modulated to data phase only */ #define SOC_RMT_SUPPORT_XTAL 1 /*!< Support set XTAL clock as the RMT clock source */ #define SOC_RMT_SUPPORT_APB 1 /*!< Support set APB as the RMT clock source */ #define SOC_RMT_SUPPORT_RC_FAST 1 /*!< Support set RC_FAST clock as the RMT clock source */ diff --git a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in index 38f0158102..06422606d7 100644 --- a/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32h2/include/soc/Kconfig.soc_caps.in @@ -403,7 +403,7 @@ config SOC_RMT_SUPPORT_TX_SYNCHRO bool default y -config SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON +config SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY bool default y diff --git a/components/soc/esp32h2/include/soc/soc_caps.h b/components/soc/esp32h2/include/soc/soc_caps.h index 284bc2b6a0..41e0a55fde 100644 --- a/components/soc/esp32h2/include/soc/soc_caps.h +++ b/components/soc/esp32h2/include/soc/soc_caps.h @@ -206,7 +206,7 @@ #define SOC_RMT_SUPPORT_TX_ASYNC_STOP 1 /*!< Support stop transmission asynchronously */ #define SOC_RMT_SUPPORT_TX_LOOP_COUNT 1 /*!< Support transmit specified number of cycles in loop mode */ #define SOC_RMT_SUPPORT_TX_SYNCHRO 1 /*!< Support coordinate a group of TX channels to start simultaneously */ -#define SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON 1 /*!< TX carrier can be modulated all the time */ +#define SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY 1 /*!< TX carrier can be modulated to data phase only */ #define SOC_RMT_SUPPORT_XTAL 1 /*!< Support set XTAL clock as the RMT clock source */ #define SOC_RMT_SUPPORT_AHB 1 /*!< Support set AHB clock as the RMT clock source */ #define SOC_RMT_SUPPORT_RC_FAST 1 /*!< Support set RC_FAST clock as the RMT clock source */ diff --git a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in index af274424af..0f6d879339 100644 --- a/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s2/include/soc/Kconfig.soc_caps.in @@ -451,7 +451,7 @@ config SOC_RMT_SUPPORT_TX_SYNCHRO bool default y -config SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON +config SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY bool default y diff --git a/components/soc/esp32s2/include/soc/soc_caps.h b/components/soc/esp32s2/include/soc/soc_caps.h index a48d92f693..0509d88a07 100644 --- a/components/soc/esp32s2/include/soc/soc_caps.h +++ b/components/soc/esp32s2/include/soc/soc_caps.h @@ -213,7 +213,7 @@ #define SOC_RMT_SUPPORT_TX_ASYNC_STOP 1 /*!< Support stop transmission asynchronously */ #define SOC_RMT_SUPPORT_TX_LOOP_COUNT 1 /*!< Support transmiting specified number of cycles in loop mode */ #define SOC_RMT_SUPPORT_TX_SYNCHRO 1 /*!< Support coordinate a group of TX channels to start simultaneously */ -#define SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON 1 /*!< TX carrier can be modulated all the time */ +#define SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY 1 /*!< TX carrier can be modulated to data phase only */ #define SOC_RMT_SUPPORT_REF_TICK 1 /*!< Support set REF_TICK as the RMT clock source */ #define SOC_RMT_SUPPORT_APB 1 /*!< Support set APB as the RMT clock source */ #define SOC_RMT_CHANNEL_CLK_INDEPENDENT 1 /*!< Can select different source clock for each channel */ diff --git a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in index 12b2d2c90f..107ffc6699 100644 --- a/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in +++ b/components/soc/esp32s3/include/soc/Kconfig.soc_caps.in @@ -503,7 +503,7 @@ config SOC_RMT_SUPPORT_TX_SYNCHRO bool default y -config SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON +config SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY bool default y diff --git a/components/soc/esp32s3/include/soc/soc_caps.h b/components/soc/esp32s3/include/soc/soc_caps.h index 1e4ac04d72..6520239cea 100644 --- a/components/soc/esp32s3/include/soc/soc_caps.h +++ b/components/soc/esp32s3/include/soc/soc_caps.h @@ -205,7 +205,7 @@ #define SOC_RMT_SUPPORT_TX_LOOP_COUNT 1 /*!< Support transmit specified number of cycles in loop mode */ #define SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP 1 /*!< Hardware support of auto-stop in loop mode */ #define SOC_RMT_SUPPORT_TX_SYNCHRO 1 /*!< Support coordinate a group of TX channels to start simultaneously */ -#define SOC_RMT_SUPPORT_TX_CARRIER_ALWAYS_ON 1 /*!< TX carrier can be modulated all the time */ +#define SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY 1 /*!< TX carrier can be modulated to data phase only */ #define SOC_RMT_SUPPORT_XTAL 1 /*!< Support set XTAL clock as the RMT clock source */ #define SOC_RMT_SUPPORT_RC_FAST 1 /*!< Support set RC_FAST clock as the RMT clock source */ #define SOC_RMT_SUPPORT_APB 1 /*!< Support set APB as the RMT clock source */ From c5cd86ae8b577edf065a8f8dc148c81b2deaf3bd Mon Sep 17 00:00:00 2001 From: morris Date: Fri, 22 Apr 2022 11:43:38 +0800 Subject: [PATCH 04/10] test_utils: migrate to use new rmt driver --- components/driver/pulse_cnt.c | 2 +- .../test_utils/ref_clock_impl_rmt_pcnt.c | 81 +++++++++---------- 2 files changed, 39 insertions(+), 44 deletions(-) diff --git a/components/driver/pulse_cnt.c b/components/driver/pulse_cnt.c index 2b81fb2a27..0533e77151 100644 --- a/components/driver/pulse_cnt.c +++ b/components/driver/pulse_cnt.c @@ -293,7 +293,7 @@ esp_err_t pcnt_unit_enable(pcnt_unit_handle_t unit) esp_err_t pcnt_unit_disable(pcnt_unit_handle_t unit) { ESP_RETURN_ON_FALSE(unit, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); - ESP_RETURN_ON_FALSE(unit->fsm = PCNT_UNIT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "unit not in enable state"); + ESP_RETURN_ON_FALSE(unit->fsm == PCNT_UNIT_FSM_ENABLE, ESP_ERR_INVALID_STATE, TAG, "unit not in enable state"); // disable interrupt service if (unit->intr) { diff --git a/tools/unit-test-app/components/test_utils/ref_clock_impl_rmt_pcnt.c b/tools/unit-test-app/components/test_utils/ref_clock_impl_rmt_pcnt.c index 14ce9dc992..81bbdd78c1 100644 --- a/tools/unit-test-app/components/test_utils/ref_clock_impl_rmt_pcnt.c +++ b/tools/unit-test-app/components/test_utils/ref_clock_impl_rmt_pcnt.c @@ -23,35 +23,22 @@ #include "unity.h" #include "test_utils.h" #include "freertos/FreeRTOS.h" -#include "esp_intr_alloc.h" -#include "esp_private/periph_ctrl.h" #include "driver/pulse_cnt.h" -#include "driver/rmt_types_legacy.h" -#include "soc/gpio_sig_map.h" -#include "soc/gpio_periph.h" -#include "soc/soc_caps.h" -#include "hal/rmt_types.h" -#include "hal/rmt_hal.h" -#include "hal/rmt_ll.h" -#include "esp_rom_gpio.h" -#include "esp_rom_sys.h" +#include "driver/rmt_tx.h" #if !CONFIG_IDF_TARGET_ESP32 #error "RMT+PCNT timestamp workaround is only for ESP32" #endif -#define REF_CLOCK_RMT_CHANNEL 0 // RMT channel 0 -#define REF_CLOCK_GPIO 21 // GPIO used to combine RMT out signal with PCNT input signal +#define REF_CLOCK_GPIO 0 // GPIO used to combine RMT out signal with PCNT input signal #define REF_CLOCK_PRESCALER_MS 30 // PCNT high threshold interrupt fired every 30ms -static rmt_hal_context_t s_rmt_hal; static pcnt_unit_handle_t s_pcnt_unit; static pcnt_channel_handle_t s_pcnt_chan; +static rmt_channel_handle_t s_rmt_chan; +static rmt_encoder_handle_t s_rmt_encoder; static volatile uint32_t s_milliseconds; -// RMTMEM address is declared in .peripherals.ld -extern rmt_mem_t RMTMEM; - static bool on_reach_watch_point(pcnt_unit_handle_t unit, pcnt_watch_event_data_t *edata, void *user_ctx) { s_milliseconds += REF_CLOCK_PRESCALER_MS; @@ -88,33 +75,40 @@ void ref_clock_init(void) // start pcnt TEST_ESP_OK(pcnt_unit_start(s_pcnt_unit)); - // Route RMT output to GPIO matrix - esp_rom_gpio_connect_out_signal(REF_CLOCK_GPIO, RMT_SIG_OUT0_IDX, false, false); - // Initialize RMT - periph_module_enable(PERIPH_RMT_MODULE); - rmt_hal_init(&s_rmt_hal); - - rmt_item32_t data = { - .duration0 = 1, - .level0 = 1, - .duration1 = 0, - .level1 = 0 + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_APB_F1M, // REF_TICK clock source + .gpio_num = REF_CLOCK_GPIO, + .mem_block_symbols = 64, + .resolution_hz = 10000, // channel resolution doesn't really matter, because we only utilize the carrier + .trans_queue_depth = 1, + .flags.io_loop_back = true, }; + TEST_ESP_OK(rmt_new_tx_channel(&tx_chan_config, &s_rmt_chan)); + // set carrier configuration + rmt_carrier_config_t carrier_config = { + .duty_cycle = 0.5, + .frequency_hz = 500 * 1000, // 500 KHz + }; + TEST_ESP_OK(rmt_apply_carrier(s_rmt_chan, &carrier_config)); + // enable rmt channel + TEST_ESP_OK(rmt_enable(s_rmt_chan)); + // create a copy encoder to copy the RMT symbol into RMT HW memory + rmt_copy_encoder_config_t encoder_config = {}; + TEST_ESP_OK(rmt_new_copy_encoder(&encoder_config, &s_rmt_encoder)); - rmt_ll_enable_periph_clock(s_rmt_hal.regs, true); - rmt_ll_set_group_clock_src(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, RMT_CLK_SRC_APB_F1M, 1, 1, 0); // select REF_TICK (1MHz) - rmt_ll_tx_set_channel_clock_div(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1); // channel clock = REF_TICK / 1 = 1MHz - rmt_ll_tx_fix_idle_level(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1, true); // enable idle output, idle level: 1 - rmt_ll_tx_enable_carrier_modulation(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, true); - rmt_ll_tx_set_carrier_high_low_ticks(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1, 1); // set carrier to 1MHz / (1+1) = 500KHz, 50% duty cycle - rmt_ll_tx_set_carrier_level(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1); - rmt_ll_enable_mem_access_nonfifo(s_rmt_hal.regs, true); - rmt_ll_tx_reset_pointer(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL); - rmt_ll_tx_set_mem_blocks(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, 1); - RMTMEM.chan[REF_CLOCK_RMT_CHANNEL].data32[0] = data; - rmt_ll_tx_enable_loop(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, false); - rmt_ll_tx_start(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL); + // control the tx channel to output a fixed high level by constructing the following RMT symbol + // the carrier is modulated to the high level by default, which results in a 500KHz carrier on the `REF_CLOCK_GPIO` + rmt_symbol_word_t data = { + .level0 = 1, + .duration0 = 1, + .level1 = 1, + .duration1 = 0, + }; + rmt_transmit_config_t trans_config = { + .loop_count = 0, // no loop + }; + TEST_ESP_OK(rmt_transmit(s_rmt_chan, s_rmt_encoder, &data, sizeof(data), &trans_config)); s_milliseconds = 0; } @@ -129,8 +123,9 @@ void ref_clock_deinit(void) TEST_ESP_OK(pcnt_del_unit(s_pcnt_unit)); // Deinitialize RMT - rmt_ll_tx_enable_carrier_modulation(s_rmt_hal.regs, REF_CLOCK_RMT_CHANNEL, false); - periph_module_disable(PERIPH_RMT_MODULE); + TEST_ESP_OK(rmt_disable(s_rmt_chan)); + TEST_ESP_OK(rmt_del_channel(s_rmt_chan)); + TEST_ESP_OK(rmt_del_encoder(s_rmt_encoder)); } uint64_t ref_clock_get(void) From 977a2830dd4f97a69be0934d7f136f3e1c12c49b Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 7 Apr 2022 13:10:57 +0800 Subject: [PATCH 05/10] example: update ir nec example with new rmt driver --- .gitlab/ci/target-test.yml | 16 +- .../CMakeLists.txt | 2 +- .../rmt/ir_nec_transceiver/README.md | 110 +++++++++ .../ir_nec_transceiver/main/CMakeLists.txt | 2 + .../ir_nec_transceiver/main/ir_nec_encoder.c | 154 ++++++++++++ .../ir_nec_transceiver/main/ir_nec_encoder.h | 44 ++++ .../main/ir_nec_transceiver_main.c | 233 ++++++++++++++++++ .../rmt/ir_nec_transceiver/pytest_ir_nec.py | 19 ++ .../peripherals/rmt/ir_protocols/README.md | 76 ------ .../rmt/ir_protocols/example_test.py | 28 --- .../rmt/ir_protocols/main/CMakeLists.txt | 2 - .../rmt/ir_protocols/main/Kconfig.projbuild | 46 ---- .../rmt/ir_protocols/main/ir_protocols_main.c | 118 --------- .../rmt/ir_protocols/sdkconfig.ci.nec | 1 - .../rmt/ir_protocols/sdkconfig.ci.rc5 | 1 - pytest.ini | 1 + tools/ci/check_copyright_ignore.txt | 2 - 17 files changed, 574 insertions(+), 281 deletions(-) rename examples/peripherals/rmt/{ir_protocols => ir_nec_transceiver}/CMakeLists.txt (88%) create mode 100644 examples/peripherals/rmt/ir_nec_transceiver/README.md create mode 100644 examples/peripherals/rmt/ir_nec_transceiver/main/CMakeLists.txt create mode 100644 examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_encoder.c create mode 100644 examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_encoder.h create mode 100644 examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_transceiver_main.c create mode 100644 examples/peripherals/rmt/ir_nec_transceiver/pytest_ir_nec.py delete mode 100644 examples/peripherals/rmt/ir_protocols/README.md delete mode 100644 examples/peripherals/rmt/ir_protocols/example_test.py delete mode 100644 examples/peripherals/rmt/ir_protocols/main/CMakeLists.txt delete mode 100644 examples/peripherals/rmt/ir_protocols/main/Kconfig.projbuild delete mode 100644 examples/peripherals/rmt/ir_protocols/main/ir_protocols_main.c delete mode 100644 examples/peripherals/rmt/ir_protocols/sdkconfig.ci.nec delete mode 100644 examples/peripherals/rmt/ir_protocols/sdkconfig.ci.rc5 diff --git a/.gitlab/ci/target-test.yml b/.gitlab/ci/target-test.yml index 64baf5e58c..5bd1881995 100644 --- a/.gitlab/ci/target-test.yml +++ b/.gitlab/ci/target-test.yml @@ -31,6 +31,16 @@ example_test_pytest_esp32_generic: TARGET: ESP32 ENV_MARKER: generic +example_test_pytest_esp32_ir_transceiver: + extends: + - .pytest_examples_dir_template + - .rules:test:example_test-esp32 + needs: + - build_pytest_examples_esp32 + variables: + TARGET: ESP32 + ENV_MARKER: ir_transceiver + example_test_pytest_esp32s2_generic: extends: - .pytest_examples_dir_template @@ -462,12 +472,6 @@ example_test_011: variables: SETUP_TOOLS: "1" -example_test_012: - extends: .example_test_esp32_template - tags: - - ESP32 - - Example_RMT_IR_PROTOCOLS - example_test_013: extends: .example_test_esp32_template tags: diff --git a/examples/peripherals/rmt/ir_protocols/CMakeLists.txt b/examples/peripherals/rmt/ir_nec_transceiver/CMakeLists.txt similarity index 88% rename from examples/peripherals/rmt/ir_protocols/CMakeLists.txt rename to examples/peripherals/rmt/ir_nec_transceiver/CMakeLists.txt index d86759d964..7449870442 100644 --- a/examples/peripherals/rmt/ir_protocols/CMakeLists.txt +++ b/examples/peripherals/rmt/ir_nec_transceiver/CMakeLists.txt @@ -3,4 +3,4 @@ cmake_minimum_required(VERSION 3.5) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(ir_protocols) +project(ir_nec_transceiver) diff --git a/examples/peripherals/rmt/ir_nec_transceiver/README.md b/examples/peripherals/rmt/ir_nec_transceiver/README.md new file mode 100644 index 0000000000..e71daa2c8b --- /dev/null +++ b/examples/peripherals/rmt/ir_nec_transceiver/README.md @@ -0,0 +1,110 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-S3 | ESP32-C3 | +| ----------------- | ----- | -------- | -------- | -------- | +# IR NEC Encoding and Decoding Example + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +[NEC](https://www.sbprojects.net/knowledge/ir/nec.php) is a common use IR protocol, this example creates a TX channel and sends out the IR NEC signals periodically. The signal is modulated with a 38KHz carrier. The example also creates an RX channel, to receive and parse the IR NEC signals into scan codes. + +## How to Use Example + +### Hardware Required + +* A development board with supported SoC mentioned in the above `Supported Targets` table +* An USB cable for power supply and programming +* A 5mm infrared LED (e.g. IR333C) used to transmit encoded IR signals +* An infrared receiver module (e.g. IRM-3638T), which integrates a demodulator and AGC circuit + +### Hardware Connection + +``` + IR Receiver (IRM-3638T) ESP Board IR Transmitter (IR333C) ++--------------------------+ +----------------------+ +---------------------------+ +| RX+-------+IR_RX_GPIO IR_TX_GPIO+--------------+TX | +| | | | | | +| 3V3+-------+3V3 5V+--------------+VCC | +| | | | | | +| GND+-------+GND GND+--------------+GND | ++--------------------------+ +----------------------+ +---------------------------+ +``` + +The TX and RX GPIO number used by this example can be changed in [the source file](main/ir_nec_transceiver_main.c) via `EXAMPLE_IR_TX_GPIO_NUM` and `EXAMPLE_IR_RX_GPIO_NUM`. + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Example Output + +Run this example, you might see the following output log: + +``` +... +I (0) cpu_start: Starting scheduler on APP CPU. +I (306) example: create RMT RX channel +I (306) gpio: GPIO[19]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (316) example: register RX done callback +I (316) example: create RMT TX channel +I (326) gpio: GPIO[18]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (336) example: modulate carrier to TX channel +I (336) example: install IR NEC encoder +I (346) example: start RMT TX and RX channel + +NEC frame start--- +{0:9020},{1:4461} +{0:577},{1:577} +{0:577},{1:576} +{0:552},{1:601} +{0:552},{1:601} +{0:577},{1:576} +{0:578},{1:575} +{0:583},{1:572} +{0:579},{1:574} +{0:576},{1:1648} +{0:553},{1:1673} +{0:579},{1:1647} +{0:580},{1:1645} +{0:577},{1:1649} +{0:554},{1:1673} +{0:578},{1:1648} +{0:553},{1:1673} +{0:555},{1:1671} +{0:578},{1:577} +{0:553},{1:1673} +{0:554},{1:1671} +{0:555},{1:601} +{0:580},{1:574} +{0:551},{1:603} +{0:580},{1:574} +{0:553},{1:601} +{0:553},{1:1672} +{0:554},{1:602} +{0:552},{1:603} +{0:579},{1:1646} +{0:554},{1:1672} +{0:555},{1:1672} +{0:580},{1:1646} +{0:555},{1:0} +---NEC frame end: Address=FF00, Command=F20D + +NEC frame start--- +{0:9024},{1:2213} +{0:583},{1:0} +---NEC frame end: Address=FF00, Command=F20D, repeat + +NEC frame start--- +{0:584},{1:0} +---NEC frame end: Unknown NEC frame + +... +``` + +In the example's main loop, the RX channel waits for any NEC frames, if nothing received within 1 second, the TX channel will send out a predefined NEC frame (address=0x0440, command=0x3003). + +## Troubleshooting + +For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/rmt/ir_nec_transceiver/main/CMakeLists.txt b/examples/peripherals/rmt/ir_nec_transceiver/main/CMakeLists.txt new file mode 100644 index 0000000000..129f0fa792 --- /dev/null +++ b/examples/peripherals/rmt/ir_nec_transceiver/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "ir_nec_transceiver_main.c" "ir_nec_encoder.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_encoder.c b/examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_encoder.c new file mode 100644 index 0000000000..7e33c622d4 --- /dev/null +++ b/examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_encoder.c @@ -0,0 +1,154 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_check.h" +#include "ir_nec_encoder.h" + +static const char *TAG = "nec_encoder"; + +typedef struct { + rmt_encoder_t base; // the base "class", declares the standard encoder interface + rmt_encoder_t *copy_encoder; // use the copy_encoder to encode the leading and ending pulse + rmt_encoder_t *bytes_encoder; // use the bytes_encoder to encode the address and command data + rmt_symbol_word_t nec_leading_symbol; // NEC leading code with RMT representation + rmt_symbol_word_t nec_ending_symbol; // NEC ending code with RMT representation + int state; +} rmt_ir_nec_encoder_t; + +static size_t rmt_encode_ir_nec(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_encoder_t, base); + rmt_encode_state_t session_state = 0; + rmt_encode_state_t state = 0; + size_t encoded_symbols = 0; + ir_nec_scan_code_t *scan_code = (ir_nec_scan_code_t *)primary_data; + rmt_encoder_handle_t copy_encoder = nec_encoder->copy_encoder; + rmt_encoder_handle_t bytes_encoder = nec_encoder->bytes_encoder; + switch (nec_encoder->state) { + case 0: // send leading code + encoded_symbols += copy_encoder->encode(copy_encoder, channel, &nec_encoder->nec_leading_symbol, + sizeof(rmt_symbol_word_t), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 1; // we can only switch to next state when current encoder finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + // fall-through + case 1: // send address + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->address, sizeof(uint16_t), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 2; // we can only switch to next state when current encoder finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + // fall-through + case 2: // send command + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->command, sizeof(uint16_t), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 3; // we can only switch to next state when current encoder finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + // fall-through + case 3: // send ending code + encoded_symbols += copy_encoder->encode(copy_encoder, channel, &nec_encoder->nec_ending_symbol, + sizeof(rmt_symbol_word_t), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 0; // back to the initial encoding session + state |= RMT_ENCODING_COMPLETE; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_ir_nec_encoder(rmt_encoder_t *encoder) +{ + rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_encoder_t, base); + rmt_del_encoder(nec_encoder->copy_encoder); + rmt_del_encoder(nec_encoder->bytes_encoder); + free(nec_encoder); + return ESP_OK; +} + +static esp_err_t rmt_ir_nec_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_encoder_t, base); + rmt_encoder_reset(nec_encoder->copy_encoder); + rmt_encoder_reset(nec_encoder->bytes_encoder); + nec_encoder->state = 0; + return ESP_OK; +} + +esp_err_t rmt_new_ir_nec_encoder(const ir_nec_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_ir_nec_encoder_t *nec_encoder = NULL; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + nec_encoder = calloc(1, sizeof(rmt_ir_nec_encoder_t)); + ESP_GOTO_ON_FALSE(nec_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for ir nec encoder"); + nec_encoder->base.encode = rmt_encode_ir_nec; + nec_encoder->base.del = rmt_del_ir_nec_encoder; + nec_encoder->base.reset = rmt_ir_nec_encoder_reset; + + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &nec_encoder->copy_encoder), err, TAG, "create copy encoder failed"); + + // construct the leading code and ending code with RMT symbol format + nec_encoder->nec_leading_symbol = (rmt_symbol_word_t) { + .level0 = 1, + .duration0 = 9000ULL * config->resolution / 1000000, + .level1 = 0, + .duration1 = 4500ULL * config->resolution / 1000000, + }; + nec_encoder->nec_ending_symbol = (rmt_symbol_word_t) { + .level0 = 1, + .duration0 = 560 * config->resolution / 1000000, + .level1 = 0, + .duration1 = 0x7FFF, + }; + + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 560 * config->resolution / 1000000, // T0H=560us + .level1 = 0, + .duration1 = 560 * config->resolution / 1000000, // T0L=560us + }, + .bit1 = { + .level0 = 1, + .duration0 = 560 * config->resolution / 1000000, // T1H=560us + .level1 = 0, + .duration1 = 1690 * config->resolution / 1000000, // T1L=1690us + }, + }; + ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &nec_encoder->bytes_encoder), err, TAG, "create bytes encoder failed"); + + *ret_encoder = &nec_encoder->base; + return ESP_OK; +err: + if (nec_encoder) { + if (nec_encoder->bytes_encoder) { + rmt_del_encoder(nec_encoder->bytes_encoder); + } + if (nec_encoder->copy_encoder) { + rmt_del_encoder(nec_encoder->copy_encoder); + } + free(nec_encoder); + } + return ret; +} diff --git a/examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_encoder.h b/examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_encoder.h new file mode 100644 index 0000000000..9bfadfeb5b --- /dev/null +++ b/examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_encoder.h @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief IR NEC scan code representation + */ +typedef struct { + uint16_t address; + uint16_t command; +} ir_nec_scan_code_t; + +/** + * @brief Type of IR NEC encoder configuration + */ +typedef struct { + uint32_t resolution; /*!< Encoder resolution, in Hz */ +} ir_nec_encoder_config_t; + +/** + * @brief Create RMT encoder for encoding IR NEC frame into RMT symbols + * + * @param[in] config Encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_ERR_INVALID_ARG for any invalid arguments + * - ESP_ERR_NO_MEM out of memory when creating IR NEC encoder + * - ESP_OK if creating encoder successfully + */ +esp_err_t rmt_new_ir_nec_encoder(const ir_nec_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_transceiver_main.c b/examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_transceiver_main.c new file mode 100644 index 0000000000..c07a054b38 --- /dev/null +++ b/examples/peripherals/rmt/ir_nec_transceiver/main/ir_nec_transceiver_main.c @@ -0,0 +1,233 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/rmt_tx.h" +#include "driver/rmt_rx.h" +#include "ir_nec_encoder.h" + +#define EXAMPLE_IR_RESOLUTION_HZ 1000000 // 1MHz resolution, 1 tick = 1us +#define EXAMPLE_IR_TX_GPIO_NUM 18 +#define EXAMPLE_IR_RX_GPIO_NUM 19 +#define EXAMPLE_IR_NEC_DECODE_MARGIN 200 // Tolerance for parsing RMT symbols into bit stream + +/** + * @brief NEC timing spec + */ +#define NEC_LEADING_CODE_DURATION_0 9000 +#define NEC_LEADING_CODE_DURATION_1 4500 +#define NEC_PAYLOAD_ZERO_DURATION_0 560 +#define NEC_PAYLOAD_ZERO_DURATION_1 560 +#define NEC_PAYLOAD_ONE_DURATION_0 560 +#define NEC_PAYLOAD_ONE_DURATION_1 1690 +#define NEC_REPEAT_CODE_DURATION_0 9000 +#define NEC_REPEAT_CODE_DURATION_1 2250 + +static const char *TAG = "example"; + +/** + * @brief Saving NEC decode results + */ +static uint16_t s_nec_code_address; +static uint16_t s_nec_code_command; + +/** + * @brief Check whether a duration is within expected range + */ +static inline bool nec_check_in_range(uint32_t signal_duration, uint32_t spec_duration) +{ + return (signal_duration < (spec_duration + EXAMPLE_IR_NEC_DECODE_MARGIN)) && + (signal_duration > (spec_duration - EXAMPLE_IR_NEC_DECODE_MARGIN)); +} + +/** + * @brief Check whether a RMT symbol represents NEC logic zero + */ +static bool nec_parse_logic0(rmt_symbol_word_t *rmt_nec_symbols) +{ + return nec_check_in_range(rmt_nec_symbols->duration0, NEC_PAYLOAD_ZERO_DURATION_0) && + nec_check_in_range(rmt_nec_symbols->duration1, NEC_PAYLOAD_ZERO_DURATION_1); +} + +/** + * @brief Check whether a RMT symbol represents NEC logic one + */ +static bool nec_parse_logic1(rmt_symbol_word_t *rmt_nec_symbols) +{ + return nec_check_in_range(rmt_nec_symbols->duration0, NEC_PAYLOAD_ONE_DURATION_0) && + nec_check_in_range(rmt_nec_symbols->duration1, NEC_PAYLOAD_ONE_DURATION_1); +} + +/** + * @brief Decode RMT symbols into NEC address and command + */ +static bool nec_parse_frame(rmt_symbol_word_t *rmt_nec_symbols) +{ + rmt_symbol_word_t *cur = rmt_nec_symbols; + uint16_t address = 0; + uint16_t command = 0; + bool valid_leading_code = nec_check_in_range(cur->duration0, NEC_LEADING_CODE_DURATION_0) && + nec_check_in_range(cur->duration1, NEC_LEADING_CODE_DURATION_1); + if (!valid_leading_code) { + return false; + } + cur++; + for (int i = 0; i < 16; i++) { + if (nec_parse_logic1(cur)) { + address |= 1 << i; + } else if (nec_parse_logic0(cur)) { + address &= ~(1 << i); + } else { + return false; + } + cur++; + } + for (int i = 0; i < 16; i++) { + if (nec_parse_logic1(cur)) { + command |= 1 << i; + } else if (nec_parse_logic0(cur)) { + command &= ~(1 << i); + } else { + return false; + } + cur++; + } + // save address and command + s_nec_code_address = address; + s_nec_code_command = command; + return true; +} + +/** + * @brief Check whether the RMT symbols represent NEC repeat code + */ +static bool nec_parse_frame_repeat(rmt_symbol_word_t *rmt_nec_symbols) +{ + return nec_check_in_range(rmt_nec_symbols->duration0, NEC_REPEAT_CODE_DURATION_0) && + nec_check_in_range(rmt_nec_symbols->duration1, NEC_REPEAT_CODE_DURATION_1); +} + +/** + * @brief Decode RMT symbols into NEC scan code and print the result + */ +static void example_parse_nec_frame(rmt_symbol_word_t *rmt_nec_symbols, size_t symbol_num) +{ + printf("NEC frame start---\r\n"); + for (size_t i = 0; i < symbol_num; i++) { + printf("{%d:%d},{%d:%d}\r\n", rmt_nec_symbols[i].level0, rmt_nec_symbols[i].duration0, + rmt_nec_symbols[i].level1, rmt_nec_symbols[i].duration1); + } + printf("---NEC frame end: "); + // decode RMT symbols + switch (symbol_num) { + case 34: // NEC normal frame + if (nec_parse_frame(rmt_nec_symbols)) { + printf("Address=%04X, Command=%04X\r\n\r\n", s_nec_code_address, s_nec_code_command); + } + break; + case 2: // NEC repeat frame + if (nec_parse_frame_repeat(rmt_nec_symbols)) { + printf("Address=%04X, Command=%04X, repeat\r\n\r\n", s_nec_code_address, s_nec_code_command); + } + break; + default: + printf("Unknown NEC frame\r\n\r\n"); + break; + } +} + +static bool example_rmt_rx_done_callback(rmt_channel_handle_t channel, rmt_rx_done_event_data_t *edata, void *user_data) +{ + BaseType_t high_task_wakeup = pdFALSE; + TaskHandle_t task_to_notify = (TaskHandle_t)user_data; + // send the received RMT symbols to the parser task + xTaskNotifyFromISR(task_to_notify, (uint32_t)edata, eSetValueWithOverwrite, &high_task_wakeup); + return high_task_wakeup == pdTRUE; +} + +void app_main(void) +{ + ESP_LOGI(TAG, "create RMT RX channel"); + rmt_rx_channel_config_t rx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = EXAMPLE_IR_RESOLUTION_HZ, + .mem_block_symbols = 64, // amount of RMT symbols that the channel can store at a time + .gpio_num = EXAMPLE_IR_RX_GPIO_NUM, + }; + rmt_channel_handle_t rx_channel = NULL; + ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_channel_cfg, &rx_channel)); + + ESP_LOGI(TAG, "register RX done callback"); + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + rmt_rx_event_callbacks_t cbs = { + .on_recv_done = example_rmt_rx_done_callback, + }; + ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_channel, &cbs, cur_task)); + + // the following timing requirement is based on NEC protocol + rmt_receive_config_t receive_config = { + .signal_range_min_ns = 1250, // the shortest duration for NEC signal is 560us, 1250ns < 560us, valid signal won't be treated as noise + .signal_range_max_ns = 12000000, // the longest duration for NEC signal is 9000us, 12000000ns > 9000us, the receive won't stop early + }; + + ESP_LOGI(TAG, "create RMT TX channel"); + rmt_tx_channel_config_t tx_channel_cfg = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .resolution_hz = EXAMPLE_IR_RESOLUTION_HZ, + .mem_block_symbols = 64, // amount of RMT symbols that the channel can store at a time + .trans_queue_depth = 4, // number of transactions that allowed to pending in the background, this example won't queue multiple transactions, so queue depth > 1 is sufficient + .gpio_num = EXAMPLE_IR_TX_GPIO_NUM, + }; + rmt_channel_handle_t tx_channel = NULL; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_channel_cfg, &tx_channel)); + + ESP_LOGI(TAG, "modulate carrier to TX channel"); + rmt_carrier_config_t carrier_cfg = { + .duty_cycle = 0.33, + .frequency_hz = 38000, // 38KHz + }; + ESP_ERROR_CHECK(rmt_apply_carrier(tx_channel, &carrier_cfg)); + + // this example won't send NEC frames in a loop + rmt_transmit_config_t transmit_config = { + .loop_count = 0, // no loop + }; + + ESP_LOGI(TAG, "install IR NEC encoder"); + ir_nec_encoder_config_t nec_encoder_cfg = { + .resolution = EXAMPLE_IR_RESOLUTION_HZ, + }; + rmt_encoder_handle_t nec_encoder = NULL; + ESP_ERROR_CHECK(rmt_new_ir_nec_encoder(&nec_encoder_cfg, &nec_encoder)); + + ESP_LOGI(TAG, "enable RMT TX and RX channels"); + ESP_ERROR_CHECK(rmt_enable(tx_channel)); + ESP_ERROR_CHECK(rmt_enable(rx_channel)); + + // save the received RMT symbols + rmt_symbol_word_t raw_symbols[64]; // 64 symbols should be sufficient for a standard NEC frame + rmt_rx_done_event_data_t *rx_data = NULL; + // ready to receive + ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config)); + while (1) { + // wait for RX done signal + if (xTaskNotifyWait(0x00, ULONG_MAX, (uint32_t *)&rx_data, pdMS_TO_TICKS(1000)) == pdTRUE) { + // parse the receive symbols and print the result + example_parse_nec_frame(rx_data->received_symbols, rx_data->num_symbols); + // start receive again + ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config)); + } else { + // timeout, transmit predefined IR NEC packets + const ir_nec_scan_code_t scan_code = { + .address = 0x0440, + .command = 0x3003, + }; + ESP_ERROR_CHECK(rmt_transmit(tx_channel, nec_encoder, &scan_code, sizeof(scan_code), &transmit_config)); + } + } +} diff --git a/examples/peripherals/rmt/ir_nec_transceiver/pytest_ir_nec.py b/examples/peripherals/rmt/ir_nec_transceiver/pytest_ir_nec.py new file mode 100644 index 0000000000..7c6979a62a --- /dev/null +++ b/examples/peripherals/rmt/ir_nec_transceiver/pytest_ir_nec.py @@ -0,0 +1,19 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.ir_transceiver +def test_ir_nec_example(dut: Dut) -> None: + dut.expect_exact('example: create RMT RX channel') + dut.expect_exact('example: register RX done callback') + dut.expect_exact('example: create RMT TX channel') + dut.expect_exact('example: modulate carrier to TX channel') + dut.expect_exact('example: install IR NEC encoder') + dut.expect_exact('example: enable RMT TX and RX channels') + dut.expect_exact('NEC frame start---') + dut.expect_exact('---NEC frame end: Address=0440, Command=3003') + dut.expect_exact('---NEC frame end: Address=0440, Command=3003') diff --git a/examples/peripherals/rmt/ir_protocols/README.md b/examples/peripherals/rmt/ir_protocols/README.md deleted file mode 100644 index b76bb39198..0000000000 --- a/examples/peripherals/rmt/ir_protocols/README.md +++ /dev/null @@ -1,76 +0,0 @@ -| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 | -| ----------------- | ----- | -------- | -------- | -------- | -# IR Protocol Example - -(See the README.md file in the upper level 'examples' directory for more information about examples.) - -This example illustrates how to encode and decode RMT signals with/to common IR protocols (e.g. NEC and RC5). - -[NEC](https://www.sbprojects.net/knowledge/ir/nec.php) and [RC5](https://www.sbprojects.net/knowledge/ir/rc5.php) have different encoding rules, but both can be compatible to RMT data format. - -The example supports building and parsing both normal and extended NEC/RC5 protocol. And also supports `repeat code` which would be sent out if one remote key got pressed for a specific long time. - -## How to Use Example - -### Hardware Required - -* A development board with supported SoC mentioned in the above `Supported Targets` table -* An USB cable for power supply and programming -* A 5mm infrared LED (e.g. IR333C) used to transmit encoded IR signals -* An infrared receiver module (e.g. IRM-3638T), which integrates a demodulator and AGC circuit. - -Example connection : - -| ESP chip | IR333C | IRM-3638T | -| --------------------------- | ------ | --------- | -| CONFIG_EXAMPLE_RMT_TX_GPIO | Tx | × | -| CONFIG_EXAMPLE_RMT_RX_GPIO | × | Rx | -| VCC 5V | √ | × | -| VCC 3.3V | × | √ | -| GND | GND | GND | - - -### Configure the Project - -Open the project configuration menu (`idf.py menuconfig`). - -In the `Example Configuration` menu: - -* Select the infrared protocol used in the example under `Infrared Protocol` option. -* Set the GPIO number used for transmitting the IR signal under `RMT TX GPIO` option. -* Set the GPIO number used for receiving the demodulated IR signal under `RMT RX GPIO` option. -* Set the RMT TX channel number under `RMT TX Channel Number` option. -* Set the RMT RX channel number under `RMT RX Channel Number` option. - -### Build and Flash - -Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. - -(To exit the serial monitor, type ``Ctrl-]``.) - -See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. - -## Example Output - -Run this example, you will see the following output log (for NEC protocol): -``` -I (2000) example: Send command 0x20 to address 0x10 -I (2070) example: Scan Code --- addr: 0x0010 cmd: 0x0020 -I (2220) example: Scan Code (repeat) --- addr: 0x0010 cmd: 0x0020 -I (4240) example: Send command 0x21 to address 0x10 -I (4310) example: Scan Code --- addr: 0x0010 cmd: 0x0021 -I (4460) example: Scan Code (repeat) --- addr: 0x0010 cmd: 0x0021 -I (6480) example: Send command 0x22 to address 0x10 -I (6550) example: Scan Code --- addr: 0x0010 cmd: 0x0022 -I (6700) example: Scan Code (repeat) --- addr: 0x0010 cmd: 0x0022 -I (8720) example: Send command 0x23 to address 0x10 -I (8790) example: Scan Code --- addr: 0x0010 cmd: 0x0023 -I (8940) example: Scan Code (repeat) --- addr: 0x0010 cmd: 0x0023 -I (10960) example: Send command 0x24 to address 0x10 -I (11030) example: Scan Code --- addr: 0x0010 cmd: 0x0024 -I (11180) example: Scan Code (repeat) --- addr: 0x0010 cmd: 0x0024 -``` - -## Troubleshooting - -For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/rmt/ir_protocols/example_test.py b/examples/peripherals/rmt/ir_protocols/example_test.py deleted file mode 100644 index 6501a42c48..0000000000 --- a/examples/peripherals/rmt/ir_protocols/example_test.py +++ /dev/null @@ -1,28 +0,0 @@ -from __future__ import print_function - -import ttfw_idf - -EXPECT_TIMEOUT = 20 - - -@ttfw_idf.idf_example_test(env_tag='Example_RMT_IR_PROTOCOLS') -def test_examples_rmt_ir_protocols(env, extra_data): - dut = env.get_dut('ir_protocols_example', 'examples/peripherals/rmt/ir_protocols', app_config_name='nec') - print('Using binary path: {}'.format(dut.app.binary_path)) - dut.start_app() - dut.expect('example: Send command 0x20 to address 0x10', timeout=EXPECT_TIMEOUT) - dut.expect('Scan Code --- addr: 0x0010 cmd: 0x0020', timeout=EXPECT_TIMEOUT) - dut.expect('Scan Code (repeat) --- addr: 0x0010 cmd: 0x0020', timeout=EXPECT_TIMEOUT) - env.close_dut(dut.name) - - dut = env.get_dut('ir_protocols_example', 'examples/peripherals/rmt/ir_protocols', app_config_name='rc5') - print('Using binary path: {}'.format(dut.app.binary_path)) - dut.start_app() - dut.expect('example: Send command 0x20 to address 0x10', timeout=EXPECT_TIMEOUT) - dut.expect('Scan Code --- addr: 0x0010 cmd: 0x0020', timeout=EXPECT_TIMEOUT) - dut.expect('Scan Code (repeat) --- addr: 0x0010 cmd: 0x0020', timeout=EXPECT_TIMEOUT) - env.close_dut(dut.name) - - -if __name__ == '__main__': - test_examples_rmt_ir_protocols() diff --git a/examples/peripherals/rmt/ir_protocols/main/CMakeLists.txt b/examples/peripherals/rmt/ir_protocols/main/CMakeLists.txt deleted file mode 100644 index 29f1712d85..0000000000 --- a/examples/peripherals/rmt/ir_protocols/main/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -idf_component_register(SRCS "ir_protocols_main.c" - INCLUDE_DIRS ".") diff --git a/examples/peripherals/rmt/ir_protocols/main/Kconfig.projbuild b/examples/peripherals/rmt/ir_protocols/main/Kconfig.projbuild deleted file mode 100644 index 1f71c507bc..0000000000 --- a/examples/peripherals/rmt/ir_protocols/main/Kconfig.projbuild +++ /dev/null @@ -1,46 +0,0 @@ -menu "Example Configuration" - choice EXAMPLE_IR_PROTOCOL - prompt "Infrared Protocol" - default EXAMPLE_IR_PROTOCOL_NEC - help - Choose the IR protocol used in the example. - - config EXAMPLE_IR_PROTOCOL_NEC - bool "NEC" - help - NEC is a kind of Pulse Distance Protocol. - It uses ASK modulation and pulse distance encoding with a carrier frequency of 38 kHz. - - config EXAMPLE_IR_PROTOCOL_RC5 - bool "RC5" - help - The RC5 protocol was introduced by Philips. - It uses ASK modulation and Manchester encoding with carrier frequency fixed at 36 kHz. - - endchoice - - config EXAMPLE_RMT_TX_GPIO - int "RMT TX GPIO" - default 18 - help - Set the GPIO number used for transmitting the RMT signal. - - config EXAMPLE_RMT_RX_GPIO - int "RMT RX GPIO" - default 19 - help - Set the GPIO number used for receiving the RMT signal. - - config EXAMPLE_RMT_TX_CHANNEL - int "RMT TX Channel Number" - default 0 - help - Set the RMT TX channel number. - - config EXAMPLE_RMT_RX_CHANNEL - int "RMT RX Channel Number" - default 4 if IDF_TARGET_ESP32S3 - default 2 - help - Set the RMT RX channel number. -endmenu diff --git a/examples/peripherals/rmt/ir_protocols/main/ir_protocols_main.c b/examples/peripherals/rmt/ir_protocols/main/ir_protocols_main.c deleted file mode 100644 index 4380b275c2..0000000000 --- a/examples/peripherals/rmt/ir_protocols/main/ir_protocols_main.c +++ /dev/null @@ -1,118 +0,0 @@ -/* IR protocols example - - This example code is in the Public Domain (or CC0 licensed, at your option.) - - Unless required by applicable law or agreed to in writing, this - software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. -*/ -#include -#include -#include "sdkconfig.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_log.h" -#include "driver/rmt.h" -#include "ir_tools.h" - -static const char *TAG = "example"; - -static rmt_channel_t example_tx_channel = CONFIG_EXAMPLE_RMT_TX_CHANNEL; -static rmt_channel_t example_rx_channel = CONFIG_EXAMPLE_RMT_RX_CHANNEL; - -/** - * @brief RMT Receive Task - * - */ -static void example_ir_rx_task(void *arg) -{ - uint32_t addr = 0; - uint32_t cmd = 0; - size_t length = 0; - bool repeat = false; - RingbufHandle_t rb = NULL; - rmt_item32_t *items = NULL; - - rmt_config_t rmt_rx_config = RMT_DEFAULT_CONFIG_RX(CONFIG_EXAMPLE_RMT_RX_GPIO, example_rx_channel); - rmt_config(&rmt_rx_config); - rmt_driver_install(example_rx_channel, 1000, 0); - ir_parser_config_t ir_parser_config = IR_PARSER_DEFAULT_CONFIG((ir_dev_t)example_rx_channel); - ir_parser_config.flags |= IR_TOOLS_FLAGS_PROTO_EXT; // Using extended IR protocols (both NEC and RC5 have extended version) - ir_parser_t *ir_parser = NULL; -#if CONFIG_EXAMPLE_IR_PROTOCOL_NEC - ir_parser = ir_parser_rmt_new_nec(&ir_parser_config); -#elif CONFIG_EXAMPLE_IR_PROTOCOL_RC5 - ir_parser = ir_parser_rmt_new_rc5(&ir_parser_config); -#endif - - //get RMT RX ringbuffer - rmt_get_ringbuf_handle(example_rx_channel, &rb); - assert(rb != NULL); - // Start receive - rmt_rx_start(example_rx_channel, true); - while (1) { - items = (rmt_item32_t *) xRingbufferReceive(rb, &length, portMAX_DELAY); - if (items) { - length /= 4; // one RMT = 4 Bytes - if (ir_parser->input(ir_parser, items, length) == ESP_OK) { - if (ir_parser->get_scan_code(ir_parser, &addr, &cmd, &repeat) == ESP_OK) { - ESP_LOGI(TAG, "Scan Code %s --- addr: 0x%04x cmd: 0x%04x", repeat ? "(repeat)" : "", addr, cmd); - } - } - //after parsing the data, return spaces to ringbuffer. - vRingbufferReturnItem(rb, (void *) items); - } - } - ir_parser->del(ir_parser); - rmt_driver_uninstall(example_rx_channel); - vTaskDelete(NULL); -} - -/** - * @brief RMT Transmit Task - * - */ -static void example_ir_tx_task(void *arg) -{ - uint32_t addr = 0x10; - uint32_t cmd = 0x20; - rmt_item32_t *items = NULL; - size_t length = 0; - ir_builder_t *ir_builder = NULL; - - rmt_config_t rmt_tx_config = RMT_DEFAULT_CONFIG_TX(CONFIG_EXAMPLE_RMT_TX_GPIO, example_tx_channel); - rmt_tx_config.tx_config.carrier_en = true; - rmt_config(&rmt_tx_config); - rmt_driver_install(example_tx_channel, 0, 0); - ir_builder_config_t ir_builder_config = IR_BUILDER_DEFAULT_CONFIG((ir_dev_t)example_tx_channel); - ir_builder_config.flags |= IR_TOOLS_FLAGS_PROTO_EXT; // Using extended IR protocols (both NEC and RC5 have extended version) -#if CONFIG_EXAMPLE_IR_PROTOCOL_NEC - ir_builder = ir_builder_rmt_new_nec(&ir_builder_config); -#elif CONFIG_EXAMPLE_IR_PROTOCOL_RC5 - ir_builder = ir_builder_rmt_new_rc5(&ir_builder_config); -#endif - while (1) { - vTaskDelay(pdMS_TO_TICKS(2000)); - ESP_LOGI(TAG, "Send command 0x%x to address 0x%x", cmd, addr); - // Send new key code - ESP_ERROR_CHECK(ir_builder->build_frame(ir_builder, addr, cmd)); - ESP_ERROR_CHECK(ir_builder->get_result(ir_builder, &items, &length)); - //To send data according to the waveform items. - rmt_write_items(example_tx_channel, items, length, false); - // Send repeat code - vTaskDelay(pdMS_TO_TICKS(ir_builder->repeat_period_ms)); - ESP_ERROR_CHECK(ir_builder->build_repeat_frame(ir_builder)); - ESP_ERROR_CHECK(ir_builder->get_result(ir_builder, &items, &length)); - rmt_write_items(example_tx_channel, items, length, false); - cmd++; - } - ir_builder->del(ir_builder); - rmt_driver_uninstall(example_tx_channel); - vTaskDelete(NULL); -} - -void app_main(void) -{ - xTaskCreate(example_ir_rx_task, "ir_rx_task", 2048, NULL, 10, NULL); - xTaskCreate(example_ir_tx_task, "ir_tx_task", 2048, NULL, 10, NULL); -} diff --git a/examples/peripherals/rmt/ir_protocols/sdkconfig.ci.nec b/examples/peripherals/rmt/ir_protocols/sdkconfig.ci.nec deleted file mode 100644 index a3cbde7fe5..0000000000 --- a/examples/peripherals/rmt/ir_protocols/sdkconfig.ci.nec +++ /dev/null @@ -1 +0,0 @@ -CONFIG_EXAMPLE_IR_PROTOCOL_NEC=y diff --git a/examples/peripherals/rmt/ir_protocols/sdkconfig.ci.rc5 b/examples/peripherals/rmt/ir_protocols/sdkconfig.ci.rc5 deleted file mode 100644 index de6ba4f08c..0000000000 --- a/examples/peripherals/rmt/ir_protocols/sdkconfig.ci.rc5 +++ /dev/null @@ -1 +0,0 @@ -CONFIG_EXAMPLE_IR_PROTOCOL_RC5=y diff --git a/pytest.ini b/pytest.ini index 0a59ccc4a8..da5fc442f8 100644 --- a/pytest.ini +++ b/pytest.ini @@ -32,6 +32,7 @@ markers = usb_host: usb host runners ethernet_ota: ethernet OTA runners flash_encryption: Flash Encryption runners + ir_transceiver: runners with a pair of IR transmitter and receiver # log related log_cli = True diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index 94a8e6b6b6..090540645b 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -2073,8 +2073,6 @@ examples/peripherals/mcpwm/mcpwm_brushed_dc_control/main/mcpwm_brushed_dc_contro examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/mcpwm_capture_hc_sr04.c examples/peripherals/mcpwm/mcpwm_servo_control/main/mcpwm_servo_control_example_main.c examples/peripherals/mcpwm/mcpwm_sync_example/main/mcpwm_sync_example.c -examples/peripherals/rmt/ir_protocols/example_test.py -examples/peripherals/rmt/ir_protocols/main/ir_protocols_main.c examples/peripherals/rmt/led_strip/main/led_strip_main.c examples/peripherals/rmt/morse_code/main/morse_code_main.c examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/include/musical_buzzer.h From d537da5bfbe8ec677f3927e995310c09ea8783fc Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 7 Apr 2022 13:12:16 +0800 Subject: [PATCH 06/10] example: update led strip example with new rmt driver --- .../led_strip/CMakeLists.txt | 7 +- .../common_components/led_strip/README.md | 3 +- .../led_strip/include/led_strip.h | 192 ++++++---------- .../led_strip/interface/led_strip_interface.h | 78 +++++++ .../led_strip/led_strip_rmt_ws2812.c | 216 ------------------ .../led_strip/src/led_strip_rmt_dev.c | 136 +++++++++++ .../led_strip/src/led_strip_rmt_encoder.c | 124 ++++++++++ .../led_strip/src/led_strip_rmt_encoder.h | 36 +++ examples/get-started/blink/README.md | 6 +- .../get-started/blink/main/Kconfig.projbuild | 12 - .../blink/main/blink_example_main.c | 17 +- examples/get-started/blink/sdkconfig.ci | 0 examples/get-started/blink/sdkconfig.defaults | 1 - .../peripherals/rmt/led_strip/CMakeLists.txt | 2 - examples/peripherals/rmt/led_strip/README.md | 44 ++-- .../rmt/led_strip/main/CMakeLists.txt | 2 +- .../rmt/led_strip/main/Kconfig.projbuild | 13 -- .../rmt/led_strip/main/led_strip_encoder.c | 124 ++++++++++ .../rmt/led_strip/main/led_strip_encoder.h | 36 +++ .../led_strip/main/led_strip_example_main.c | 128 +++++++++++ .../rmt/led_strip/main/led_strip_main.c | 116 ---------- .../rmt/led_strip/pytest_led_strip.py | 17 ++ .../light_bulb/main/light_driver.c | 15 +- .../light_bulb/main/light_driver.h | 8 +- tools/ci/check_copyright_ignore.txt | 2 - 25 files changed, 803 insertions(+), 532 deletions(-) create mode 100644 examples/common_components/led_strip/interface/led_strip_interface.h delete mode 100644 examples/common_components/led_strip/led_strip_rmt_ws2812.c create mode 100644 examples/common_components/led_strip/src/led_strip_rmt_dev.c create mode 100644 examples/common_components/led_strip/src/led_strip_rmt_encoder.c create mode 100644 examples/common_components/led_strip/src/led_strip_rmt_encoder.h delete mode 100644 examples/get-started/blink/sdkconfig.ci delete mode 100644 examples/get-started/blink/sdkconfig.defaults delete mode 100644 examples/peripherals/rmt/led_strip/main/Kconfig.projbuild create mode 100644 examples/peripherals/rmt/led_strip/main/led_strip_encoder.c create mode 100644 examples/peripherals/rmt/led_strip/main/led_strip_encoder.h create mode 100644 examples/peripherals/rmt/led_strip/main/led_strip_example_main.c delete mode 100644 examples/peripherals/rmt/led_strip/main/led_strip_main.c create mode 100644 examples/peripherals/rmt/led_strip/pytest_led_strip.py diff --git a/examples/common_components/led_strip/CMakeLists.txt b/examples/common_components/led_strip/CMakeLists.txt index fe4ca413a2..a9c4b523a2 100644 --- a/examples/common_components/led_strip/CMakeLists.txt +++ b/examples/common_components/led_strip/CMakeLists.txt @@ -1,4 +1,3 @@ -idf_component_register(SRCS "led_strip_rmt_ws2812.c" - INCLUDE_DIRS "include" - PRIV_REQUIRES "driver" - ) +idf_component_register(SRCS "src/led_strip_rmt_dev.c" "src/led_strip_rmt_encoder.c" + INCLUDE_DIRS "include" "interface" + PRIV_REQUIRES "driver") diff --git a/examples/common_components/led_strip/README.md b/examples/common_components/led_strip/README.md index abf07898a5..8f3dae326e 100644 --- a/examples/common_components/led_strip/README.md +++ b/examples/common_components/led_strip/README.md @@ -1,6 +1,6 @@ # LED Strip Component -This directory contains an implementation for addressable LEDs using the RMT peripheral. +This directory contains an implementation for addressable LEDs by different peripherals. Currently only RMT is supported as the led strip backend. It's compatible with: @@ -9,7 +9,6 @@ It's compatible with: This component is used as part of the following ESP-IDF examples: - [Blink Example](../../get-started/blink). -- [LED Strip Example](../../peripherals/rmt/led_strip). To learn more about how to use this component, please check API Documentation from header file [led_strip.h](./include/led_strip.h). diff --git a/examples/common_components/led_strip/include/led_strip.h b/examples/common_components/led_strip/include/led_strip.h index 2e1d633495..d49a530ca4 100644 --- a/examples/common_components/led_strip/include/led_strip.h +++ b/examples/common_components/led_strip/include/led_strip.h @@ -1,146 +1,94 @@ -// Copyright 2019 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. +/* + * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ #pragma once +#include +#include "esp_err.h" + #ifdef __cplusplus extern "C" { #endif -#include "esp_err.h" +/** + * @brief LED strip handle + */ +typedef struct led_strip_t *led_strip_handle_t; /** -* @brief LED Strip Type -* -*/ -typedef struct led_strip_s led_strip_t; + * @brief Set RGB for a specific pixel + * + * @param strip: LED strip + * @param index: index of pixel to set + * @param red: red part of color + * @param green: green part of color + * @param blue: blue part of color + * + * @return + * - ESP_OK: Set RGB for a specific pixel successfully + * - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters + * - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred + */ +esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); /** -* @brief LED Strip Device Type -* -*/ -typedef void *led_strip_dev_t; + * @brief Refresh memory colors to LEDs + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Refresh successfully + * - ESP_FAIL: Refresh failed because some other error occurred + * + * @note: + * After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip. + */ +esp_err_t led_strip_refresh(led_strip_handle_t strip); /** -* @brief Declare of LED Strip Type -* -*/ -struct led_strip_s { - /** - * @brief Set RGB for a specific pixel - * - * @param strip: LED strip - * @param index: index of pixel to set - * @param red: red part of color - * @param green: green part of color - * @param blue: blue part of color - * - * @return - * - ESP_OK: Set RGB for a specific pixel successfully - * - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters - * - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred - */ - esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); - - /** - * @brief Refresh memory colors to LEDs - * - * @param strip: LED strip - * @param timeout_ms: timeout value for refreshing task - * - * @return - * - ESP_OK: Refresh successfully - * - ESP_ERR_TIMEOUT: Refresh failed because of timeout - * - ESP_FAIL: Refresh failed because some other error occurred - * - * @note: - * After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip. - */ - esp_err_t (*refresh)(led_strip_t *strip, uint32_t timeout_ms); - - /** - * @brief Clear LED strip (turn off all LEDs) - * - * @param strip: LED strip - * @param timeout_ms: timeout value for clearing task - * - * @return - * - ESP_OK: Clear LEDs successfully - * - ESP_ERR_TIMEOUT: Clear LEDs failed because of timeout - * - ESP_FAIL: Clear LEDs failed because some other error occurred - */ - esp_err_t (*clear)(led_strip_t *strip, uint32_t timeout_ms); - - /** - * @brief Free LED strip resources - * - * @param strip: LED strip - * - * @return - * - ESP_OK: Free resources successfully - * - ESP_FAIL: Free resources failed because error occurred - */ - esp_err_t (*del)(led_strip_t *strip); -}; + * @brief Clear LED strip (turn off all LEDs) + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Clear LEDs successfully + * - ESP_FAIL: Clear LEDs failed because some other error occurred + */ +esp_err_t led_strip_clear(led_strip_handle_t strip); /** -* @brief LED Strip Configuration Type -* -*/ + * @brief Free LED strip resources + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Free resources successfully + * - ESP_FAIL: Free resources failed because error occurred + */ +esp_err_t led_strip_del(led_strip_handle_t strip); + +/** + * @brief LED Strip Configuration + */ typedef struct { - uint32_t max_leds; /*!< Maximum LEDs in a single strip */ - led_strip_dev_t dev; /*!< LED strip device (e.g. RMT channel, PWM channel, etc) */ + uint32_t strip_gpio_num; /*!< GPIO number that used by LED strip */ + uint32_t max_leds; /*!< Maximum LEDs in a single strip */ } led_strip_config_t; /** - * @brief Default configuration for LED strip + * @brief Create LED strip based on RMT TX channel * - */ -#define LED_STRIP_DEFAULT_CONFIG(number, dev_hdl) \ - { \ - .max_leds = number, \ - .dev = dev_hdl, \ - } - -/** -* @brief Install a new ws2812 driver (based on RMT peripheral) -* -* @param config: LED strip configuration -* @return -* LED strip instance or NULL -*/ -led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config); - -/** - * @brief Init the RMT peripheral and LED strip configuration. - * - * @param[in] channel: RMT peripheral channel number. - * @param[in] gpio: GPIO number for the RMT data output. - * @param[in] led_num: number of addressable LEDs. + * @param config LED strip specific configuration + * @param ret_strip Returned LED strip handle * @return - * LED strip instance or NULL + * - ESP_OK: create LED strip handle successfully + * - ESP_ERR_INVALID_ARG: create LED strip handle failed because of invalid argument + * - ESP_ERR_NO_MEM: create LED strip handle failed because of out of memory + * - ESP_FAIL: create LED strip handle failed because some other error */ -led_strip_t * led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num); - -/** - * @brief Denit the RMT peripheral. - * - * @param[in] strip: LED strip - * @return - * - ESP_OK - * - ESP_FAIL - */ -esp_err_t led_strip_denit(led_strip_t *strip); +esp_err_t led_strip_new_rmt_device(const led_strip_config_t *config, led_strip_handle_t *ret_strip); #ifdef __cplusplus } diff --git a/examples/common_components/led_strip/interface/led_strip_interface.h b/examples/common_components/led_strip/interface/led_strip_interface.h new file mode 100644 index 0000000000..3ba15df2ba --- /dev/null +++ b/examples/common_components/led_strip/interface/led_strip_interface.h @@ -0,0 +1,78 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "esp_err.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct led_strip_t led_strip_t; /*!< Type of LED strip */ + +/** + * @brief LED strip interface definition + */ +struct led_strip_t { + /** + * @brief Set RGB for a specific pixel + * + * @param strip: LED strip + * @param index: index of pixel to set + * @param red: red part of color + * @param green: green part of color + * @param blue: blue part of color + * + * @return + * - ESP_OK: Set RGB for a specific pixel successfully + * - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters + * - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred + */ + esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); + + /** + * @brief Refresh memory colors to LEDs + * + * @param strip: LED strip + * @param timeout_ms: timeout value for refreshing task + * + * @return + * - ESP_OK: Refresh successfully + * - ESP_FAIL: Refresh failed because some other error occurred + * + * @note: + * After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip. + */ + esp_err_t (*refresh)(led_strip_t *strip); + + /** + * @brief Clear LED strip (turn off all LEDs) + * + * @param strip: LED strip + * @param timeout_ms: timeout value for clearing task + * + * @return + * - ESP_OK: Clear LEDs successfully + * - ESP_FAIL: Clear LEDs failed because some other error occurred + */ + esp_err_t (*clear)(led_strip_t *strip); + + /** + * @brief Free LED strip resources + * + * @param strip: LED strip + * + * @return + * - ESP_OK: Free resources successfully + * - ESP_FAIL: Free resources failed because error occurred + */ + esp_err_t (*del)(led_strip_t *strip); +}; + +#ifdef __cplusplus +} +#endif diff --git a/examples/common_components/led_strip/led_strip_rmt_ws2812.c b/examples/common_components/led_strip/led_strip_rmt_ws2812.c deleted file mode 100644 index bd9af41c39..0000000000 --- a/examples/common_components/led_strip/led_strip_rmt_ws2812.c +++ /dev/null @@ -1,216 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: Apache-2.0 - */ -#include -#include -#include -#include "esp_log.h" -#include "esp_attr.h" -#include "led_strip.h" -#include "driver/rmt.h" - -#define RMT_TX_CHANNEL RMT_CHANNEL_0 - -static const char *TAG = "ws2812"; -#define STRIP_CHECK(a, str, goto_tag, ret_value, ...) \ - do \ - { \ - if (!(a)) \ - { \ - ESP_LOGE(TAG, "%s(%d): " str, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ - ret = ret_value; \ - goto goto_tag; \ - } \ - } while (0) - -#define WS2812_T0H_NS (350) -#define WS2812_T0L_NS (1000) -#define WS2812_T1H_NS (1000) -#define WS2812_T1L_NS (350) -#define WS2812_RESET_US (280) - -static uint32_t ws2812_t0h_ticks = 0; -static uint32_t ws2812_t1h_ticks = 0; -static uint32_t ws2812_t0l_ticks = 0; -static uint32_t ws2812_t1l_ticks = 0; -static uint32_t ws2812_reset_ticks = 0; - -typedef struct { - led_strip_t parent; - rmt_channel_t rmt_channel; - uint32_t strip_len; - uint8_t buffer[0]; -} ws2812_t; - -/** - * @brief Conver RGB data to RMT format. - * - * @note For WS2812, R,G,B each contains 256 different choices (i.e. uint8_t) - * - * @param[in] src: source data, to converted to RMT format - * @param[in] dest: place where to store the convert result - * @param[in] src_size: size of source data - * @param[in] wanted_num: number of RMT items that want to get - * @param[out] translated_size: number of source data that got converted - * @param[out] item_num: number of RMT items which are converted from source data - */ -static void IRAM_ATTR ws2812_rmt_adapter(const void *src, rmt_item32_t *dest, size_t src_size, - size_t wanted_num, size_t *translated_size, size_t *item_num) -{ - if (src == NULL || dest == NULL) { - *translated_size = 0; - *item_num = 0; - return; - } - const rmt_item32_t bit0 = {{{ ws2812_t0h_ticks, 1, ws2812_t0l_ticks, 0 }}}; //Logical 0 - const rmt_item32_t bit1 = {{{ ws2812_t1h_ticks, 1, ws2812_t1l_ticks, 0 }}}; //Logical 1 - - const rmt_item32_t reset = {{{ ws2812_reset_ticks, 0, 0, 0 }}}; // Reset signal - const rmt_item32_t nop = {{{ 0, 0, 0, 0 }}}; // Null signal - - size_t size = 0; - size_t num = 0; - uint8_t *psrc = (uint8_t *)src; - rmt_item32_t *pdest = dest; - while (size < (src_size - 1) && num < wanted_num) { - // MSB first, so we decremented `i` from 7 to 0. - // Note that `i` must be signed (e.g. `int`) for this to work! - for (int i = 7; i >= 0; i--) { - (pdest++)->val = (*psrc & (1 << i)) ? bit1.val : bit0.val; - } - num += 8; - size++; - psrc++; - } - // Send reset signal (LOW for `WS2812_RESET_US` microseconds) - // The check of the size is still needed, because `src_size` could be 0. - // (Note that the check could be replaced by `src_size > 0`, - // but it is more clear this way.) - if (size < src_size && num < wanted_num) { - (pdest++)->val = reset.val; // The first bit is the reset signal - // we just sent the first bit, so we start the loop with i = 1 - for (int i = 1; i < 8; i++) { // complete the byte with 7 NOP signals - (pdest++)->val = nop.val; - } - num += 8; - size++; - // we don't need to increment `psrc`, because we no longer use it - } - - *translated_size = size; - *item_num = num; -} - -static esp_err_t ws2812_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) -{ - esp_err_t ret = ESP_OK; - ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); - STRIP_CHECK(index < ws2812->strip_len, "index out of the maximum number of leds", err, ESP_ERR_INVALID_ARG); - uint32_t start = index * 3; - // In the order of GRB - ws2812->buffer[start + 0] = green & 0xFF; - ws2812->buffer[start + 1] = red & 0xFF; - ws2812->buffer[start + 2] = blue & 0xFF; - return ESP_OK; -err: - return ret; -} - -static esp_err_t ws2812_refresh(led_strip_t *strip, uint32_t timeout_ms) -{ - esp_err_t ret = ESP_OK; - ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); - STRIP_CHECK(rmt_write_sample(ws2812->rmt_channel, ws2812->buffer, ws2812->strip_len * 3 + 1, true) == ESP_OK, - "transmit RMT samples failed", err, ESP_FAIL); - return rmt_wait_tx_done(ws2812->rmt_channel, pdMS_TO_TICKS(timeout_ms)); -err: - return ret; -} - -static esp_err_t ws2812_clear(led_strip_t *strip, uint32_t timeout_ms) -{ - ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); - // Write zero to turn off all leds - memset(ws2812->buffer, 0, ws2812->strip_len * 3); - return ws2812_refresh(strip, timeout_ms); -} - -static esp_err_t ws2812_del(led_strip_t *strip) -{ - ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); - free(ws2812); - return ESP_OK; -} - -led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config) -{ - led_strip_t *ret = NULL; - STRIP_CHECK(config, "configuration can't be null", err, NULL); - - // 24 bits per led + reset - uint32_t ws2812_size = sizeof(ws2812_t) + config->max_leds * 3 + 1; - ws2812_t *ws2812 = calloc(1, ws2812_size); - STRIP_CHECK(ws2812, "request memory for ws2812 failed", err, NULL); - - uint32_t counter_clk_hz = 0; - STRIP_CHECK(rmt_get_counter_clock((rmt_channel_t)config->dev, &counter_clk_hz) == ESP_OK, - "get rmt counter clock failed", err, NULL); - // ns -> ticks - float ratio = (float)counter_clk_hz / 1e9; - ws2812_t0h_ticks = (uint32_t)(ratio * WS2812_T0H_NS); - ws2812_t0l_ticks = (uint32_t)(ratio * WS2812_T0L_NS); - ws2812_t1h_ticks = (uint32_t)(ratio * WS2812_T1H_NS); - ws2812_t1l_ticks = (uint32_t)(ratio * WS2812_T1L_NS); - ws2812_reset_ticks = (uint32_t)(ratio * WS2812_RESET_US * 1000); - - // set ws2812 to rmt adapter - rmt_translator_init((rmt_channel_t)config->dev, ws2812_rmt_adapter); - - ws2812->rmt_channel = (rmt_channel_t)config->dev; - ws2812->strip_len = config->max_leds; - - ws2812->parent.set_pixel = ws2812_set_pixel; - ws2812->parent.refresh = ws2812_refresh; - ws2812->parent.clear = ws2812_clear; - ws2812->parent.del = ws2812_del; - - return &ws2812->parent; -err: - return ret; -} - -led_strip_t * led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num) -{ - static led_strip_t *pStrip; - - rmt_config_t config = RMT_DEFAULT_CONFIG_TX(gpio, channel); - // set counter clock to 40MHz - config.clk_div = 2; - - ESP_ERROR_CHECK(rmt_config(&config)); - ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0)); - - // install ws2812 driver - led_strip_config_t strip_config = LED_STRIP_DEFAULT_CONFIG(led_num, (led_strip_dev_t)config.channel); - - pStrip = led_strip_new_rmt_ws2812(&strip_config); - - if ( !pStrip ) { - ESP_LOGE(TAG, "install WS2812 driver failed"); - return NULL; - } - - // Clear LED strip (turn off all LEDs) - ESP_ERROR_CHECK(pStrip->clear(pStrip, 100)); - - return pStrip; -} - -esp_err_t led_strip_denit(led_strip_t *strip) -{ - ws2812_t *ws2812 = __containerof(strip, ws2812_t, parent); - ESP_ERROR_CHECK(rmt_driver_uninstall(ws2812->rmt_channel)); - return strip->del(strip); -} diff --git a/examples/common_components/led_strip/src/led_strip_rmt_dev.c b/examples/common_components/led_strip/src/led_strip_rmt_dev.c new file mode 100644 index 0000000000..fc92f30d75 --- /dev/null +++ b/examples/common_components/led_strip/src/led_strip_rmt_dev.c @@ -0,0 +1,136 @@ +/* + * SPDX-FileCopyrightText: 2019-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#include +#include +#include +#include "esp_log.h" +#include "esp_check.h" +#include "driver/rmt_tx.h" +#include "led_strip.h" +#include "led_strip_interface.h" +#include "led_strip_rmt_encoder.h" + +#define LED_SRIP_RMT_RESOLUTION 10000000 // 10MHz resolution + +static const char *TAG = "led_strip_rmt"; + +typedef struct { + led_strip_t base; + rmt_channel_handle_t rmt_chan; + rmt_encoder_handle_t strip_encoder; + uint32_t strip_len; + uint8_t pixel_buf[]; +} led_strip_rmt_obj; + +static esp_err_t led_strip_rmt_set_pixel(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) +{ + led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); + ESP_RETURN_ON_FALSE(index < rmt_strip->strip_len, ESP_ERR_INVALID_ARG, TAG, "index out of maximum number of LEDs"); + uint32_t start = index * 3; + // In thr order of GRB, as LED strip like WS2812 sends out pixels in this order + rmt_strip->pixel_buf[start + 0] = green & 0xFF; + rmt_strip->pixel_buf[start + 1] = red & 0xFF; + rmt_strip->pixel_buf[start + 2] = blue & 0xFF; + return ESP_OK; +} + +static esp_err_t led_strip_rmt_refresh(led_strip_t *strip) +{ + led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); + rmt_transmit_config_t tx_conf = { + .loop_count = 0, + }; + ESP_RETURN_ON_ERROR(rmt_transmit(rmt_strip->rmt_chan, rmt_strip->strip_encoder, rmt_strip->pixel_buf, + rmt_strip->strip_len * 3, &tx_conf), TAG, "transmit pixels by RMT failed"); + ESP_RETURN_ON_ERROR(rmt_tx_wait_all_done(rmt_strip->rmt_chan, -1), TAG, "flush RMT channel failed"); + return ESP_OK; +} + +static esp_err_t led_strip_rmt_clear(led_strip_t *strip) +{ + led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); + // Write zero to turn off all leds + memset(rmt_strip->pixel_buf, 0, rmt_strip->strip_len * 3); + return led_strip_rmt_refresh(strip); +} + +static esp_err_t led_strip_rmt_del(led_strip_t *strip) +{ + led_strip_rmt_obj *rmt_strip = __containerof(strip, led_strip_rmt_obj, base); + ESP_RETURN_ON_ERROR(rmt_disable(rmt_strip->rmt_chan), TAG, "disable RMT channel failed"); + ESP_RETURN_ON_ERROR(rmt_del_channel(rmt_strip->rmt_chan), TAG, "delete RMT channel failed"); + ESP_RETURN_ON_ERROR(rmt_del_encoder(rmt_strip->strip_encoder), TAG, "delete strip encoder failed"); + free(rmt_strip); + return ESP_OK; +} + +esp_err_t led_strip_new_rmt_device(const led_strip_config_t *config, led_strip_handle_t *ret_strip) +{ + led_strip_rmt_obj *rmt_strip = NULL; + esp_err_t ret = ESP_OK; + ESP_GOTO_ON_FALSE(config && ret_strip, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + rmt_strip = calloc(1, sizeof(led_strip_rmt_obj) + config->max_leds * 3); + ESP_GOTO_ON_FALSE(rmt_strip, ESP_ERR_NO_MEM, err, TAG, "no mem for rmt strip"); + rmt_tx_channel_config_t rmt_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, + .gpio_num = config->strip_gpio_num, + .mem_block_symbols = 64, + .resolution_hz = LED_SRIP_RMT_RESOLUTION, + .trans_queue_depth = 4, + }; + ESP_GOTO_ON_ERROR(rmt_new_tx_channel(&rmt_chan_config, &rmt_strip->rmt_chan), err, TAG, "create RMT TX channel failed"); + + led_strip_encoder_config_t strip_encoder_conf = { + .resolution = LED_SRIP_RMT_RESOLUTION, + }; + ESP_GOTO_ON_ERROR(rmt_new_led_strip_encoder(&strip_encoder_conf, &rmt_strip->strip_encoder), err, TAG, "create LED strip encoder failed"); + + ESP_GOTO_ON_ERROR(rmt_enable(rmt_strip->rmt_chan), err, TAG, "enable RMT channel failed"); + + rmt_strip->strip_len = config->max_leds; + rmt_strip->base.set_pixel = led_strip_rmt_set_pixel; + rmt_strip->base.refresh = led_strip_rmt_refresh; + rmt_strip->base.clear = led_strip_rmt_clear; + rmt_strip->base.del = led_strip_rmt_del; + + *ret_strip = &rmt_strip->base; + return ESP_OK; +err: + if (rmt_strip) { + if (rmt_strip->rmt_chan) { + rmt_del_channel(rmt_strip->rmt_chan); + } + if (rmt_strip->strip_encoder) { + rmt_del_encoder(rmt_strip->strip_encoder); + } + free(rmt_strip); + } + return ret; +} + +esp_err_t led_strip_set_pixel(led_strip_handle_t strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue) +{ + ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return strip->set_pixel(strip, index, red, green, blue); +} + +esp_err_t led_strip_refresh(led_strip_handle_t strip) +{ + ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return strip->refresh(strip); +} + +esp_err_t led_strip_clear(led_strip_handle_t strip) +{ + ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return strip->clear(strip); +} + +esp_err_t led_strip_del(led_strip_handle_t strip) +{ + ESP_RETURN_ON_FALSE(strip, ESP_ERR_INVALID_ARG, TAG, "invalid argument"); + return strip->del(strip); +} diff --git a/examples/common_components/led_strip/src/led_strip_rmt_encoder.c b/examples/common_components/led_strip/src/led_strip_rmt_encoder.c new file mode 100644 index 0000000000..aa05fd3daf --- /dev/null +++ b/examples/common_components/led_strip/src/led_strip_rmt_encoder.c @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_check.h" +#include "led_strip_rmt_encoder.h" + +static const char *TAG = "led_encoder"; + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *copy_encoder; + int state; + rmt_symbol_word_t reset_code; +} rmt_led_strip_encoder_t; + +static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder; + rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder; + rmt_encode_state_t session_state = 0; + rmt_encode_state_t state = 0; + size_t encoded_symbols = 0; + switch (led_encoder->state) { + case 0: // send RGB data + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 1; // switch to next state when current encoding session finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + // fall-through + case 1: // send reset code + encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code, + sizeof(led_encoder->reset_code), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 0; // back to the initial encoding session + state |= RMT_ENCODING_COMPLETE; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_del_encoder(led_encoder->bytes_encoder); + rmt_del_encoder(led_encoder->copy_encoder); + free(led_encoder); + return ESP_OK; +} + +static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_reset(led_encoder->bytes_encoder); + rmt_encoder_reset(led_encoder->copy_encoder); + led_encoder->state = 0; + return ESP_OK; +} + +esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_led_strip_encoder_t *led_encoder = NULL; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t)); + ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder"); + led_encoder->base.encode = rmt_encode_led_strip; + led_encoder->base.del = rmt_del_led_strip_encoder; + led_encoder->base.reset = rmt_led_strip_encoder_reset; + // different led strip might have its own timing requirements, following parameter is for WS2812 + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us + .level1 = 0, + .duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us + }, + .bit1 = { + .level0 = 1, + .duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us + .level1 = 0, + .duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us + }, + .flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0 + }; + ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed"); + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed"); + + uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2; // reset code duration defaults to 50us + led_encoder->reset_code = (rmt_symbol_word_t) { + .level0 = 0, + .duration0 = reset_ticks, + .level1 = 0, + .duration1 = reset_ticks, + }; + *ret_encoder = &led_encoder->base; + return ESP_OK; +err: + if (led_encoder) { + if (led_encoder->bytes_encoder) { + rmt_del_encoder(led_encoder->bytes_encoder); + } + if (led_encoder->copy_encoder) { + rmt_del_encoder(led_encoder->copy_encoder); + } + free(led_encoder); + } + return ret; +} diff --git a/examples/common_components/led_strip/src/led_strip_rmt_encoder.h b/examples/common_components/led_strip/src/led_strip_rmt_encoder.h new file mode 100644 index 0000000000..db5ef076b3 --- /dev/null +++ b/examples/common_components/led_strip/src/led_strip_rmt_encoder.h @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of led strip encoder configuration + */ +typedef struct { + uint32_t resolution; /*!< Encoder resolution, in Hz */ +} led_strip_encoder_config_t; + +/** + * @brief Create RMT encoder for encoding LED strip pixels into RMT symbols + * + * @param[in] config Encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_ERR_INVALID_ARG for any invalid arguments + * - ESP_ERR_NO_MEM out of memory when creating led strip encoder + * - ESP_OK if creating encoder successfully + */ +esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/examples/get-started/blink/README.md b/examples/get-started/blink/README.md index f478147f0d..ae6a65eff4 100644 --- a/examples/get-started/blink/README.md +++ b/examples/get-started/blink/README.md @@ -29,14 +29,12 @@ See [Development Boards](https://www.espressif.com/en/products/devkits) for more ### Configure the Project -Open the project configuration menu (`idf.py menuconfig`). +Open the project configuration menu (`idf.py menuconfig`). In the `Example Configuration` menu: * Select the LED type in the `Blink LED type` option. * Use `GPIO` for regular LED blink. - * Use `RMT` for addressable LED blink. - * Use `RMT Channel` to select the RMT peripheral channel. * Set the GPIO number used for the signal in the `Blink GPIO number` option. * Set the blinking period in the `Blink period in ms` option. @@ -50,7 +48,7 @@ See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/l ## Example Output -As you run the example, you will see the LED blinking, according to the previously defined period. For the addressable LED, you can also change the LED color by setting the `pStrip_a->set_pixel(pStrip_a, 0, 16, 16, 16);` (LED Strip, Pixel Number, Red, Green, Blue) with values from 0 to 255 in the `blink.c` file. +As you run the example, you will see the LED blinking, according to the previously defined period. For the addressable LED, you can also change the LED color by setting the `led_strip_set_pixel(pStrip_a, 0, 16, 16, 16);` (LED Strip, Pixel Number, Red, Green, Blue) with values from 0 to 255 in the `blink.c` file. ``` I (315) example: Example configured to blink addressable LED! diff --git a/examples/get-started/blink/main/Kconfig.projbuild b/examples/get-started/blink/main/Kconfig.projbuild index 3b60f9b325..18b13a773d 100644 --- a/examples/get-started/blink/main/Kconfig.projbuild +++ b/examples/get-started/blink/main/Kconfig.projbuild @@ -13,18 +13,6 @@ menu "Example Configuration" bool "RMT - Addressable LED" endchoice - config BLINK_LED_RMT_CHANNEL - depends on BLINK_LED_RMT - int "RMT Channel" - range 0 7 - default 0 - help - Set the RMT peripheral channel. - ESP32 RMT channel from 0 to 7 - ESP32-S2 RMT channel from 0 to 3 - ESP32-S3 RMT channel from 0 to 3 - ESP32-C3 RMT channel from 0 to 1 - config BLINK_GPIO int "Blink GPIO number" range 0 48 diff --git a/examples/get-started/blink/main/blink_example_main.c b/examples/get-started/blink/main/blink_example_main.c index 94f48b2d1b..3320ead77a 100644 --- a/examples/get-started/blink/main/blink_example_main.c +++ b/examples/get-started/blink/main/blink_example_main.c @@ -24,19 +24,20 @@ static const char *TAG = "example"; static uint8_t s_led_state = 0; #ifdef CONFIG_BLINK_LED_RMT -static led_strip_t *pStrip_a; + +static led_strip_handle_t led_strip; static void blink_led(void) { /* If the addressable LED is enabled */ if (s_led_state) { /* Set the LED pixel using RGB from 0 (0%) to 255 (100%) for each color */ - pStrip_a->set_pixel(pStrip_a, 0, 16, 16, 16); + led_strip_set_pixel(led_strip, 0, 16, 16, 16); /* Refresh the strip to send data */ - pStrip_a->refresh(pStrip_a, 100); + led_strip_refresh(led_strip); } else { /* Set all LED off to clear all pixels */ - pStrip_a->clear(pStrip_a, 50); + led_strip_clear(led_strip); } } @@ -44,9 +45,13 @@ static void configure_led(void) { ESP_LOGI(TAG, "Example configured to blink addressable LED!"); /* LED strip initialization with the GPIO and pixels number*/ - pStrip_a = led_strip_init(CONFIG_BLINK_LED_RMT_CHANNEL, BLINK_GPIO, 1); + led_strip_config_t strip_config = { + .strip_gpio_num = BLINK_GPIO, + .max_leds = 1, // at least one LED on board + }; + ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &led_strip)); /* Set all LED off to clear all pixels */ - pStrip_a->clear(pStrip_a, 50); + led_strip_clear(led_strip); } #elif CONFIG_BLINK_LED_GPIO diff --git a/examples/get-started/blink/sdkconfig.ci b/examples/get-started/blink/sdkconfig.ci deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/examples/get-started/blink/sdkconfig.defaults b/examples/get-started/blink/sdkconfig.defaults deleted file mode 100644 index 792d600548..0000000000 --- a/examples/get-started/blink/sdkconfig.defaults +++ /dev/null @@ -1 +0,0 @@ -# diff --git a/examples/peripherals/rmt/led_strip/CMakeLists.txt b/examples/peripherals/rmt/led_strip/CMakeLists.txt index 020754f1e1..4ccaa43415 100644 --- a/examples/peripherals/rmt/led_strip/CMakeLists.txt +++ b/examples/peripherals/rmt/led_strip/CMakeLists.txt @@ -2,7 +2,5 @@ # in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.5) -set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/led_strip) - include($ENV{IDF_PATH}/tools/cmake/project.cmake) project(led_strip) diff --git a/examples/peripherals/rmt/led_strip/README.md b/examples/peripherals/rmt/led_strip/README.md index 23759277dd..eb4044a999 100644 --- a/examples/peripherals/rmt/led_strip/README.md +++ b/examples/peripherals/rmt/led_strip/README.md @@ -1,37 +1,34 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | # RMT Transmit Example -- LED Strip (See the README.md file in the upper level 'examples' directory for more information about examples.) -Although RMT peripheral is mainly designed for infrared remote applications, it can also support other generic protocols thanks to its flexible data format. [WS2812](http://www.world-semi.com/Certifications/WS2812B.html) is a digital RGB LED which integrates a driver circuit and a single control wire. The protocol data format defined in WS2812 is compatible to that in RMT peripheral. This example will illustrate how to drive an WS2812 LED strip based on the RMT driver. +Almost any waveform can be generated by RMT peripheral, as long as a proper encoder is implemented. The RMT encoder is used to encode user data (e.g. RGB pixels) into format that can be recognized by hardware. + +This example shows how to drive an addressable LED strip [WS2812](http://www.world-semi.com/Certifications/WS2812B.html) by implementing the [led_strip_encoder](main/led_strip_encoder.c). ## How to Use Example ### Hardware Required -* A development board with ESP32 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.) +* A development board with any supported Espressif SOC chip (see `Supported Targets` table above) * A USB cable for Power supply and programming * A WS2812 LED strip Connection : ``` - --- 5V - | - + -GPIO18 +-----------------+---|>| (WS2812) - DI + - | - --- GND + --- 5V + | + + +RMT_LED_STRIP_GPIO_NUM +------ +---|>| (WS2812 LED strip) + DI + + | + --- GND ``` -### Configure the Project - -Open the project configuration menu (`idf.py menuconfig`). - -In the `Example Configuration` menu: - -* Set the GPIO number used for transmitting the IR signal under `RMT TX GPIO` option. -* Set the number of LEDs in a strip under `Number of LEDS in a strip` option. +The GPIO number used in this example can be changed according to your board, by the macro `RMT_LED_STRIP_GPIO_NUM` defined in the [source file](main/led_strip_example_main.c). The number of LEDs can be changed as well by `EXAMPLE_LED_NUMBERS`. ### Build and Flash @@ -41,11 +38,18 @@ Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. -## Example Output +## Console Output -Connect the `DI` signal of WS2812 LED strip to the GPIO you set in menuconfig. +``` +I (302) cpu_start: Starting scheduler on PRO CPU. +I (0) cpu_start: Starting scheduler on APP CPU. +I (323) example: Create RMT TX channel +I (343) example: Install led strip encoder +I (353) example: Start LED rainbow chase +``` -Run the example, you will see a rainbow chasing demonstration effect. To change the chasing speed, you can update the `EXAMPLE_CHASE_SPEED_MS` value in `led_strip_main.c` file. + +After you seeing this log, you should see a rainbow chasing demonstration pattern. To change the chasing speed, you can update the `EXAMPLE_CHASE_SPEED_MS` value in [source file](main/led_strip_example_main.c). ## Troubleshooting diff --git a/examples/peripherals/rmt/led_strip/main/CMakeLists.txt b/examples/peripherals/rmt/led_strip/main/CMakeLists.txt index a3f4b866e3..1515a1d90d 100644 --- a/examples/peripherals/rmt/led_strip/main/CMakeLists.txt +++ b/examples/peripherals/rmt/led_strip/main/CMakeLists.txt @@ -1,2 +1,2 @@ -idf_component_register(SRCS "led_strip_main.c" +idf_component_register(SRCS "led_strip_example_main.c" "led_strip_encoder.c" INCLUDE_DIRS ".") diff --git a/examples/peripherals/rmt/led_strip/main/Kconfig.projbuild b/examples/peripherals/rmt/led_strip/main/Kconfig.projbuild deleted file mode 100644 index d06e4ddb86..0000000000 --- a/examples/peripherals/rmt/led_strip/main/Kconfig.projbuild +++ /dev/null @@ -1,13 +0,0 @@ -menu "Example Configuration" - config EXAMPLE_RMT_TX_GPIO - int "RMT TX GPIO" - default 18 - help - Set the GPIO number used for transmitting the RMT signal. - - config EXAMPLE_STRIP_LED_NUMBER - int "Number of LEDS in a strip" - default 24 - help - A single RGB strip contains several LEDs. -endmenu diff --git a/examples/peripherals/rmt/led_strip/main/led_strip_encoder.c b/examples/peripherals/rmt/led_strip/main/led_strip_encoder.c new file mode 100644 index 0000000000..aff7fee9fe --- /dev/null +++ b/examples/peripherals/rmt/led_strip/main/led_strip_encoder.c @@ -0,0 +1,124 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_check.h" +#include "led_strip_encoder.h" + +static const char *TAG = "led_encoder"; + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *copy_encoder; + int state; + rmt_symbol_word_t reset_code; +} rmt_led_strip_encoder_t; + +static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder; + rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder; + rmt_encode_state_t session_state = 0; + rmt_encode_state_t state = 0; + size_t encoded_symbols = 0; + switch (led_encoder->state) { + case 0: // send RGB data + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 1; // switch to next state when current encoding session finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + // fall-through + case 1: // send reset code + encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code, + sizeof(led_encoder->reset_code), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + led_encoder->state = 0; // back to the initial encoding session + state |= RMT_ENCODING_COMPLETE; + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_del_encoder(led_encoder->bytes_encoder); + rmt_del_encoder(led_encoder->copy_encoder); + free(led_encoder); + return ESP_OK; +} + +static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); + rmt_encoder_reset(led_encoder->bytes_encoder); + rmt_encoder_reset(led_encoder->copy_encoder); + led_encoder->state = 0; + return ESP_OK; +} + +esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_led_strip_encoder_t *led_encoder = NULL; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t)); + ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder"); + led_encoder->base.encode = rmt_encode_led_strip; + led_encoder->base.del = rmt_del_led_strip_encoder; + led_encoder->base.reset = rmt_led_strip_encoder_reset; + // different led strip might have its own timing requirements, following parameter is for WS2812 + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us + .level1 = 0, + .duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us + }, + .bit1 = { + .level0 = 1, + .duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us + .level1 = 0, + .duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us + }, + .flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0 + }; + ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed"); + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed"); + + uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2; // reset code duration defaults to 50us + led_encoder->reset_code = (rmt_symbol_word_t) { + .level0 = 0, + .duration0 = reset_ticks, + .level1 = 0, + .duration1 = reset_ticks, + }; + *ret_encoder = &led_encoder->base; + return ESP_OK; +err: + if (led_encoder) { + if (led_encoder->bytes_encoder) { + rmt_del_encoder(led_encoder->bytes_encoder); + } + if (led_encoder->copy_encoder) { + rmt_del_encoder(led_encoder->copy_encoder); + } + free(led_encoder); + } + return ret; +} diff --git a/examples/peripherals/rmt/led_strip/main/led_strip_encoder.h b/examples/peripherals/rmt/led_strip/main/led_strip_encoder.h new file mode 100644 index 0000000000..db5ef076b3 --- /dev/null +++ b/examples/peripherals/rmt/led_strip/main/led_strip_encoder.h @@ -0,0 +1,36 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of led strip encoder configuration + */ +typedef struct { + uint32_t resolution; /*!< Encoder resolution, in Hz */ +} led_strip_encoder_config_t; + +/** + * @brief Create RMT encoder for encoding LED strip pixels into RMT symbols + * + * @param[in] config Encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_ERR_INVALID_ARG for any invalid arguments + * - ESP_ERR_NO_MEM out of memory when creating led strip encoder + * - ESP_OK if creating encoder successfully + */ +esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/rmt/led_strip/main/led_strip_example_main.c b/examples/peripherals/rmt/led_strip/main/led_strip_example_main.c new file mode 100644 index 0000000000..f29c32184d --- /dev/null +++ b/examples/peripherals/rmt/led_strip/main/led_strip_example_main.c @@ -0,0 +1,128 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ +#include +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/rmt_tx.h" +#include "led_strip_encoder.h" + + +#define RMT_LED_STRIP_RESOLUTION_HZ 10000000 // 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution) +#define RMT_LED_STRIP_GPIO_NUM 0 + +#define EXAMPLE_LED_NUMBERS 24 +#define EXAMPLE_CHASE_SPEED_MS 10 + +static const char *TAG = "example"; + +static uint8_t led_strip_pixels[EXAMPLE_LED_NUMBERS * 3]; + +/** + * @brief Simple helper function, converting HSV color space to RGB color space + * + * Wiki: https://en.wikipedia.org/wiki/HSL_and_HSV + * + */ +void led_strip_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b) +{ + h %= 360; // h -> [0,360] + uint32_t rgb_max = v * 2.55f; + uint32_t rgb_min = rgb_max * (100 - s) / 100.0f; + + uint32_t i = h / 60; + uint32_t diff = h % 60; + + // RGB adjustment amount by hue + uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60; + + switch (i) { + case 0: + *r = rgb_max; + *g = rgb_min + rgb_adj; + *b = rgb_min; + break; + case 1: + *r = rgb_max - rgb_adj; + *g = rgb_max; + *b = rgb_min; + break; + case 2: + *r = rgb_min; + *g = rgb_max; + *b = rgb_min + rgb_adj; + break; + case 3: + *r = rgb_min; + *g = rgb_max - rgb_adj; + *b = rgb_max; + break; + case 4: + *r = rgb_min + rgb_adj; + *g = rgb_min; + *b = rgb_max; + break; + default: + *r = rgb_max; + *g = rgb_min; + *b = rgb_max - rgb_adj; + break; + } +} + +void app_main(void) +{ + uint32_t red = 0; + uint32_t green = 0; + uint32_t blue = 0; + uint16_t hue = 0; + uint16_t start_rgb = 0; + + ESP_LOGI(TAG, "Create RMT TX channel"); + rmt_channel_handle_t led_chan = NULL; + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, // select source clock + .gpio_num = RMT_LED_STRIP_GPIO_NUM, + .mem_block_symbols = 64, // increase the block size can make the LED less flickering + .resolution_hz = RMT_LED_STRIP_RESOLUTION_HZ, + .trans_queue_depth = 4, // set the number of transactions that can be pending in the background + }; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &led_chan)); + + ESP_LOGI(TAG, "Install led strip encoder"); + rmt_encoder_handle_t led_encoder = NULL; + led_strip_encoder_config_t encoder_config = { + .resolution = RMT_LED_STRIP_RESOLUTION_HZ, + }; + ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&encoder_config, &led_encoder)); + + ESP_LOGI(TAG, "Enable RMT TX channel"); + ESP_ERROR_CHECK(rmt_enable(led_chan)); + + ESP_LOGI(TAG, "Start LED rainbow chase"); + rmt_transmit_config_t tx_config = { + .loop_count = 0, // no transfer loop + }; + while (1) { + for (int i = 0; i < 3; i++) { + for (int j = i; j < EXAMPLE_LED_NUMBERS; j += 3) { + // Build RGB pixels + hue = j * 360 / EXAMPLE_LED_NUMBERS + start_rgb; + led_strip_hsv2rgb(hue, 100, 100, &red, &green, &blue); + led_strip_pixels[j * 3 + 0] = green; + led_strip_pixels[j * 3 + 1] = blue; + led_strip_pixels[j * 3 + 2] = red; + } + // Flush RGB values to LEDs + ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, led_strip_pixels, sizeof(led_strip_pixels), &tx_config)); + vTaskDelay(pdMS_TO_TICKS(EXAMPLE_CHASE_SPEED_MS)); + memset(led_strip_pixels, 0, sizeof(led_strip_pixels)); + ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, led_strip_pixels, sizeof(led_strip_pixels), &tx_config)); + vTaskDelay(pdMS_TO_TICKS(EXAMPLE_CHASE_SPEED_MS)); + } + start_rgb += 60; + } +} diff --git a/examples/peripherals/rmt/led_strip/main/led_strip_main.c b/examples/peripherals/rmt/led_strip/main/led_strip_main.c deleted file mode 100644 index 895f1a4fdb..0000000000 --- a/examples/peripherals/rmt/led_strip/main/led_strip_main.c +++ /dev/null @@ -1,116 +0,0 @@ -/* RMT example -- RGB LED Strip - - This example code is in the Public Domain (or CC0 licensed, at your option.) - - Unless required by applicable law or agreed to in writing, this - software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. -*/ -#include "sdkconfig.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_log.h" -#include "driver/rmt.h" -#include "led_strip.h" - -static const char *TAG = "example"; - -#define RMT_TX_CHANNEL RMT_CHANNEL_0 - -#define EXAMPLE_CHASE_SPEED_MS (10) - -/** - * @brief Simple helper function, converting HSV color space to RGB color space - * - * Wiki: https://en.wikipedia.org/wiki/HSL_and_HSV - * - */ -void led_strip_hsv2rgb(uint32_t h, uint32_t s, uint32_t v, uint32_t *r, uint32_t *g, uint32_t *b) -{ - h %= 360; // h -> [0,360] - uint32_t rgb_max = v * 2.55f; - uint32_t rgb_min = rgb_max * (100 - s) / 100.0f; - - uint32_t i = h / 60; - uint32_t diff = h % 60; - - // RGB adjustment amount by hue - uint32_t rgb_adj = (rgb_max - rgb_min) * diff / 60; - - switch (i) { - case 0: - *r = rgb_max; - *g = rgb_min + rgb_adj; - *b = rgb_min; - break; - case 1: - *r = rgb_max - rgb_adj; - *g = rgb_max; - *b = rgb_min; - break; - case 2: - *r = rgb_min; - *g = rgb_max; - *b = rgb_min + rgb_adj; - break; - case 3: - *r = rgb_min; - *g = rgb_max - rgb_adj; - *b = rgb_max; - break; - case 4: - *r = rgb_min + rgb_adj; - *g = rgb_min; - *b = rgb_max; - break; - default: - *r = rgb_max; - *g = rgb_min; - *b = rgb_max - rgb_adj; - break; - } -} - -void app_main(void) -{ - uint32_t red = 0; - uint32_t green = 0; - uint32_t blue = 0; - uint16_t hue = 0; - uint16_t start_rgb = 0; - - rmt_config_t config = RMT_DEFAULT_CONFIG_TX(CONFIG_EXAMPLE_RMT_TX_GPIO, RMT_TX_CHANNEL); - // set counter clock to 40MHz - config.clk_div = 2; - - ESP_ERROR_CHECK(rmt_config(&config)); - ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0)); - - // install ws2812 driver - led_strip_config_t strip_config = LED_STRIP_DEFAULT_CONFIG(CONFIG_EXAMPLE_STRIP_LED_NUMBER, (led_strip_dev_t)config.channel); - led_strip_t *strip = led_strip_new_rmt_ws2812(&strip_config); - if (!strip) { - ESP_LOGE(TAG, "install WS2812 driver failed"); - } - // Clear LED strip (turn off all LEDs) - ESP_ERROR_CHECK(strip->clear(strip, 100)); - // Show simple rainbow chasing pattern - ESP_LOGI(TAG, "LED Rainbow Chase Start"); - while (true) { - for (int i = 0; i < 3; i++) { - for (int j = i; j < CONFIG_EXAMPLE_STRIP_LED_NUMBER; j += 3) { - // Build RGB values - hue = j * 360 / CONFIG_EXAMPLE_STRIP_LED_NUMBER + start_rgb; - led_strip_hsv2rgb(hue, 100, 100, &red, &green, &blue); - // Write RGB values to strip driver - ESP_ERROR_CHECK(strip->set_pixel(strip, j, red, green, blue)); - } - // Flush RGB values to LEDs - ESP_ERROR_CHECK(strip->refresh(strip, 100)); - vTaskDelay(pdMS_TO_TICKS(EXAMPLE_CHASE_SPEED_MS)); - strip->clear(strip, 50); - vTaskDelay(pdMS_TO_TICKS(EXAMPLE_CHASE_SPEED_MS)); - } - start_rgb += 60; - } -} diff --git a/examples/peripherals/rmt/led_strip/pytest_led_strip.py b/examples/peripherals/rmt/led_strip/pytest_led_strip.py new file mode 100644 index 0000000000..c3daa80356 --- /dev/null +++ b/examples/peripherals/rmt/led_strip/pytest_led_strip.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +def test_led_strip_example(dut: Dut) -> None: + dut.expect_exact('example: Create RMT TX channel') + dut.expect_exact('example: Install led strip encoder') + dut.expect_exact('example: Enable RMT TX channel') + dut.expect_exact('example: Start LED rainbow chase') diff --git a/examples/zigbee/light_sample/light_bulb/main/light_driver.c b/examples/zigbee/light_sample/light_bulb/main/light_driver.c index db7bf6b8ad..9d6413eaea 100644 --- a/examples/zigbee/light_sample/light_bulb/main/light_driver.c +++ b/examples/zigbee/light_sample/light_bulb/main/light_driver.c @@ -39,17 +39,20 @@ #include "led_strip.h" #include "light_driver.h" -static led_strip_t *led_strip; +static led_strip_handle_t s_led_strip; void light_driver_set_power(bool power) { - ESP_ERROR_CHECK(led_strip->set_pixel(led_strip, 0, 255 * power, 255 * power, 255 * power)); - ESP_ERROR_CHECK(led_strip->refresh(led_strip, 100)); + ESP_ERROR_CHECK(led_strip_set_pixel(s_led_strip, 0, 255 * power, 255 * power, 255 * power)); + ESP_ERROR_CHECK(led_strip_refresh(s_led_strip)); } -int light_driver_init(bool power) +void light_driver_init(bool power) { - led_strip = led_strip_init(RMT_TX_CHANNEL, CONFIG_EXAMPLE_RMT_TX_GPIO, CONFIG_EXAMPLE_STRIP_LED_NUMBER); + led_strip_config_t led_strip_conf = { + .max_leds = CONFIG_EXAMPLE_STRIP_LED_NUMBER, + .strip_gpio_num = CONFIG_EXAMPLE_STRIP_LED_GPIO, + }; + ESP_ERROR_CHECK(led_strip_new_rmt_device(&led_strip_conf, &s_led_strip)); light_driver_set_power(power); - return ESP_OK; } diff --git a/examples/zigbee/light_sample/light_bulb/main/light_driver.h b/examples/zigbee/light_sample/light_bulb/main/light_driver.h index fc56c6244e..7297c26386 100644 --- a/examples/zigbee/light_sample/light_bulb/main/light_driver.h +++ b/examples/zigbee/light_sample/light_bulb/main/light_driver.h @@ -37,8 +37,7 @@ #pragma once - -#include "driver/rmt.h" +#include #ifdef __cplusplus extern "C" { @@ -49,8 +48,7 @@ extern "C" { #define LIGHT_DEFAULT_OFF 0 /* LED strip configuration */ -#define RMT_TX_CHANNEL RMT_CHANNEL_0 -#define CONFIG_EXAMPLE_RMT_TX_GPIO 8 +#define CONFIG_EXAMPLE_STRIP_LED_GPIO 8 #define CONFIG_EXAMPLE_STRIP_LED_NUMBER 1 /** @@ -65,7 +63,7 @@ void light_driver_set_power(bool power); * * @param power power on/off */ -int light_driver_init(bool power); +void light_driver_init(bool power); #ifdef __cplusplus } // extern "C" diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index 090540645b..79c6888494 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -1975,7 +1975,6 @@ examples/build_system/cmake/multi_config/main/func_prod.c examples/build_system/cmake/multi_config/main/multi_config_example_main.c examples/common_components/iperf/include/iperf.h examples/common_components/iperf/iperf.c -examples/common_components/led_strip/include/led_strip.h examples/common_components/protocol_examples_common/addr_from_stdin.c examples/common_components/protocol_examples_common/connect.c examples/common_components/protocol_examples_common/include/addr_from_stdin.h @@ -2073,7 +2072,6 @@ examples/peripherals/mcpwm/mcpwm_brushed_dc_control/main/mcpwm_brushed_dc_contro examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/mcpwm_capture_hc_sr04.c examples/peripherals/mcpwm/mcpwm_servo_control/main/mcpwm_servo_control_example_main.c examples/peripherals/mcpwm/mcpwm_sync_example/main/mcpwm_sync_example.c -examples/peripherals/rmt/led_strip/main/led_strip_main.c examples/peripherals/rmt/morse_code/main/morse_code_main.c examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/include/musical_buzzer.h examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/src/musical_buzzer_rmt.c From b3c1480d9c35259fe81e1a446f6f97de87f0a3a0 Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 7 Apr 2022 13:13:28 +0800 Subject: [PATCH 07/10] example: update musical buzzer example with new rmt driver --- .../peripherals/rmt/morse_code/CMakeLists.txt | 6 - examples/peripherals/rmt/morse_code/README.md | 66 --------- .../rmt/morse_code/main/CMakeLists.txt | 2 - .../rmt/morse_code/main/Kconfig.projbuild | 7 - .../rmt/morse_code/main/morse_code_main.c | 80 ----------- .../peripherals/rmt/musical_buzzer/README.md | 55 +++---- .../components/musical_buzzer/CMakeLists.txt | 5 - .../musical_buzzer/include/musical_buzzer.h | 108 -------------- .../musical_buzzer/src/musical_buzzer_rmt.c | 135 ------------------ .../rmt/musical_buzzer/main/CMakeLists.txt | 3 +- .../main/musical_buzzer_example_main.c | 66 +++++---- .../main/musical_score_encoder.c | 74 ++++++++++ .../main/musical_score_encoder.h | 44 ++++++ .../musical_buzzer/pytest_musical_buzzer.py | 16 +++ tools/ci/check_copyright_ignore.txt | 4 - 15 files changed, 203 insertions(+), 468 deletions(-) delete mode 100644 examples/peripherals/rmt/morse_code/CMakeLists.txt delete mode 100644 examples/peripherals/rmt/morse_code/README.md delete mode 100644 examples/peripherals/rmt/morse_code/main/CMakeLists.txt delete mode 100644 examples/peripherals/rmt/morse_code/main/Kconfig.projbuild delete mode 100644 examples/peripherals/rmt/morse_code/main/morse_code_main.c delete mode 100644 examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/CMakeLists.txt delete mode 100644 examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/include/musical_buzzer.h delete mode 100644 examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/src/musical_buzzer_rmt.c create mode 100644 examples/peripherals/rmt/musical_buzzer/main/musical_score_encoder.c create mode 100644 examples/peripherals/rmt/musical_buzzer/main/musical_score_encoder.h create mode 100644 examples/peripherals/rmt/musical_buzzer/pytest_musical_buzzer.py diff --git a/examples/peripherals/rmt/morse_code/CMakeLists.txt b/examples/peripherals/rmt/morse_code/CMakeLists.txt deleted file mode 100644 index 01ca83a100..0000000000 --- a/examples/peripherals/rmt/morse_code/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -# The following lines of boilerplate have to be in your project's CMakeLists -# in this exact order for cmake to work correctly -cmake_minimum_required(VERSION 3.5) - -include($ENV{IDF_PATH}/tools/cmake/project.cmake) -project(morse_code) diff --git a/examples/peripherals/rmt/morse_code/README.md b/examples/peripherals/rmt/morse_code/README.md deleted file mode 100644 index 3f799e857e..0000000000 --- a/examples/peripherals/rmt/morse_code/README.md +++ /dev/null @@ -1,66 +0,0 @@ -# RMT Transmit Example -- Morse Code - -(See the README.md file in the upper level 'examples' directory for more information about examples.) - -This example mainly illustrates how to transmit the [Morse code](https://en.wikipedia.org/wiki/Morse_code) using the RMT driver. - -## How to Use Example - -### Hardware Required - -* A development board with ESP32 SoC (e.g., ESP32-DevKitC, ESP-WROVER-KIT, etc.) -* A USB cable for Power supply and programming -* A LED, a speaker or an earphone - -Connection : - -``` - 330R LED -GPIO18 +----/\/\/\----+------|>|-----+ GND - | - | /| - +-+ | Speaker - | | | or - +-+ | earphone - | \| - | - +--------------+ GND -``` - -### Configure the Project - -Open the project configuration menu (`idf.py menuconfig`). - -In the `Example Configuration` menu: - -* Set the GPIO number used for transmitting the IR signal under `RMT TX GPIO` optin. - -### Build and Flash - -Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. - -(To exit the serial monitor, type ``Ctrl-]``.) - -See the Getting Started Guide for all the steps to configure and use the ESP-IDF to build projects. - -* [ESP-IDF Getting Started Guide on ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/index.html) -* [ESP-IDF Getting Started Guide on ESP32-S2](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/get-started/index.html) -* [ESP-IDF Getting Started Guide on ESP32-C3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/get-started/index.html) - - -## Example Output - -To be able to see and hear the message output by the RMT, connect an LED and a speaker or an earphone (be careful it might make a large noise) to the GPIO you set in the menuconfig. - -Run the example, you will see the following output log: - -``` bash -... -I (304) example: Configuring transmitter -I (2814) example: Transmission complete -... -``` - -## Troubleshooting - -For any technical queries, please open an [issue](https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/rmt/morse_code/main/CMakeLists.txt b/examples/peripherals/rmt/morse_code/main/CMakeLists.txt deleted file mode 100644 index 870a4d9c71..0000000000 --- a/examples/peripherals/rmt/morse_code/main/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -idf_component_register(SRCS "morse_code_main.c" - INCLUDE_DIRS ".") diff --git a/examples/peripherals/rmt/morse_code/main/Kconfig.projbuild b/examples/peripherals/rmt/morse_code/main/Kconfig.projbuild deleted file mode 100644 index d596dcec79..0000000000 --- a/examples/peripherals/rmt/morse_code/main/Kconfig.projbuild +++ /dev/null @@ -1,7 +0,0 @@ -menu "Example Configuration" - config EXAMPLE_RMT_TX_GPIO - int "RMT TX GPIO" - default 18 - help - Set the GPIO number used for transmitting the RMT signal. -endmenu diff --git a/examples/peripherals/rmt/morse_code/main/morse_code_main.c b/examples/peripherals/rmt/morse_code/main/morse_code_main.c deleted file mode 100644 index d3066c8e54..0000000000 --- a/examples/peripherals/rmt/morse_code/main/morse_code_main.c +++ /dev/null @@ -1,80 +0,0 @@ -/* RMT example -- Morse Code - - This example code is in the Public Domain (or CC0 licensed, at your option.) - - Unless required by applicable law or agreed to in writing, this - software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. -*/ -#include "sdkconfig.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_log.h" -#include "driver/rmt.h" - -static const char *TAG = "example"; - -#define RMT_TX_CHANNEL RMT_CHANNEL_0 - -/* - * Prepare a raw table with a message in the Morse code - * - * The message is "ESP" : . ... .--. - * - * The table structure represents the RMT item structure: - * {duration, level, duration, level} - * - */ -static const rmt_item32_t morse_esp[] = { - // E : dot - {{{ 32767, 1, 32767, 0 }}}, // dot - {{{ 32767, 0, 32767, 0 }}}, // SPACE - // S : dot, dot, dot - {{{ 32767, 1, 32767, 0 }}}, // dot - {{{ 32767, 1, 32767, 0 }}}, // dot - {{{ 32767, 1, 32767, 0 }}}, // dot - {{{ 32767, 0, 32767, 0 }}}, // SPACE - // P : dot, dash, dash, dot - {{{ 32767, 1, 32767, 0 }}}, // dot - {{{ 32767, 1, 32767, 1 }}}, - {{{ 32767, 1, 32767, 0 }}}, // dash - {{{ 32767, 1, 32767, 1 }}}, - {{{ 32767, 1, 32767, 0 }}}, // dash - {{{ 32767, 1, 32767, 0 }}}, // dot - // RMT end marker - {{{ 0, 1, 0, 0 }}} -}; - -/* - * Initialize the RMT Tx channel - */ -static void rmt_tx_init(void) -{ - rmt_config_t config = RMT_DEFAULT_CONFIG_TX(CONFIG_EXAMPLE_RMT_TX_GPIO, RMT_TX_CHANNEL); - // enable the carrier to be able to hear the Morse sound - // if the RMT_TX_GPIO is connected to a speaker - config.tx_config.carrier_en = true; - config.tx_config.carrier_duty_percent = 50; - // set audible career frequency of 611 Hz - // actually 611 Hz is the minimum, that can be set - // with current implementation of the RMT API - config.tx_config.carrier_freq_hz = 611; - // set the maximum clock divider to be able to output - // RMT pulses in range of about one hundred milliseconds - config.clk_div = 255; - - ESP_ERROR_CHECK(rmt_config(&config)); - ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, 0)); -} - -void app_main(void) -{ - ESP_LOGI(TAG, "Configuring transmitter"); - rmt_tx_init(); - - while (1) { - ESP_ERROR_CHECK(rmt_write_items(RMT_TX_CHANNEL, morse_esp, sizeof(morse_esp) / sizeof(morse_esp[0]), true)); - ESP_LOGI(TAG, "Transmission complete"); - vTaskDelay(1000 / portTICK_PERIOD_MS); - } -} diff --git a/examples/peripherals/rmt/musical_buzzer/README.md b/examples/peripherals/rmt/musical_buzzer/README.md index 32e3c551e8..82693d6aa8 100644 --- a/examples/peripherals/rmt/musical_buzzer/README.md +++ b/examples/peripherals/rmt/musical_buzzer/README.md @@ -1,41 +1,43 @@ -| Supported Targets | ESP32-S2 | ESP32-C3 | -| ----------------- | -------- | -------- | +| Supported Targets | ESP32-S2 | ESP32-C3 | ESP32-S3 | +| ----------------- | -------- | -------- | -------- | -# RMT Transmit Loop Example -- Musical Buzzer +# RMT Transmit Loop Count Example -- Musical Buzzer (See the README.md file in the upper level 'examples' directory for more information about examples.) -RMT peripheral can send customized RMT items in a loop, which means we can use it to generate a configurable length of periodic signal. +RMT tx channel can send symbols in a loop by hardware, which is useful to generate a variable length of periodic signal. -This example will show how to drive a passive buzzer to play a simple music, based on the RMT loop feature. +This example shows how to drive a passive buzzer to play a simple music. Each musical score is represented by a constant frequency of PWM with a constant duration. To play a music is to encoding the musical score continuously. An encoder called `score_encoder` is implemented in the example, it works as a simple wrapper of the `copy_encoder`. See [musical_score_encoder](main/musical_score_encoder.c) for details. ## How to Use Example ### Hardware Required -* A development board with ESP32-S2 SoC +* A development board with any supported Espressif SOC chip (see `Supported Targets` table above) * A USB cable for Power supply and programming -* A passive buzzer +* A **passive** buzzer Connection : ``` -VCC +--------------+ - | /+ - +++ | - | | | Passive Buzzer - +++ | - | \+ - | - + | - +<----+ -GPIO +--------+ - +-----+ - + | - | -GND +--------------+ + VCC+--------------+ + | /+ + +++ | + | | | Passive Buzzer + +++ | + | \+ + | + + | + +<----+ +RMT_BUZZER_GPIO+--------+ + +-----+ + + | + | + GND+--------------+ ``` +The GPIO number used in this example can be changed according to your board, by the macro `RMT_BUZZER_GPIO_NUM` defined in the [source file](main/musical_buzzer_example_main.c). + ### Build and Flash Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. @@ -45,13 +47,18 @@ Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. -## Example Output +## Console Output ``` -I (325) example: Playing Beethoven's Ode to joy +... +I (0) cpu_start: Starting scheduler on APP CPU. +I (318) example: Create RMT TX channel +I (338) example: Install musical score encoder +I (348) example: Playing Beethoven's Ode to joy... +... ``` -After you seeing this log, you should hear the music from your buzzer. You can also play other music by updating the `notation` array in the `musical_buzzer_example_main.c`. +After you seeing this log, you should hear the music from the buzzer. To play other music, you need to change the musical score array `score` defined in the [source file](main/musical_buzzer_example_main.c). The first member declares the frequency of one musical note, and the second member declares the duration that the note should last. ## Troubleshooting diff --git a/examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/CMakeLists.txt b/examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/CMakeLists.txt deleted file mode 100644 index 55d61201f3..0000000000 --- a/examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/CMakeLists.txt +++ /dev/null @@ -1,5 +0,0 @@ -set(component_srcs "src/musical_buzzer_rmt.c") - -idf_component_register(SRCS "${component_srcs}" - INCLUDE_DIRS include - PRIV_REQUIRES driver) diff --git a/examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/include/musical_buzzer.h b/examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/include/musical_buzzer.h deleted file mode 100644 index c0a3892caa..0000000000 --- a/examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/include/musical_buzzer.h +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright 2020 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. -#pragma once - -#include "esp_err.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Type of musical buzzer interface - * - */ -typedef struct musical_buzzer_t musical_buzzer_t; - -/** - * @brief Type of musical buzzer underlying device - * - */ -typedef void *musical_buzzer_dev_t; - -/** - * @brief Type of musical buzzer notation - * - */ -typedef struct { - uint32_t note_freq_hz; /*!< Note frequency, in Hz */ - uint32_t note_duration_ms; /*!< Note duration, in ms */ -} musical_buzzer_notation_t; - -/** - * @brief Declaration of musical buzzer interface - * - */ -struct musical_buzzer_t { - /** - * @brief Start to play the given notation - * - * @param buzzer musical buzzer handle - * @param notation music notation - * @param notation_len notation length - * @return - * - ESP_OK: Start playing notation successfully - * - ESP_ERR_INVALID_ARG: wrong parameter - */ - esp_err_t (*play)(musical_buzzer_t *buzzer, const musical_buzzer_notation_t *notation, uint32_t notation_len); - - /** - * @brief Stop playing - * - * @param buzzer musical buzzer handle - * @return - * - ESP_OK: Stop playing successfully - */ - esp_err_t (*stop)(musical_buzzer_t *buzzer); - - /** - * @brief Free memory used by musical buzzer - * - * @param buzzer musical buzzer handle - * @return - * - ESP_OK: Recycle memory successfully - */ - esp_err_t (*del)(musical_buzzer_t *buzzer); -}; - -typedef struct { - musical_buzzer_dev_t dev; /*!< Musical buzzer device (e.g. RMT channel, PWM channel, etc) */ - int flags; /*!< Extra flags */ -} musical_buzzer_config_t; - -/** - * @brief Default musical buzzer configuration - * - */ -#define MUSICAL_BUZZER_DEFAULT_CONFIG(dev_hdl) \ - { \ - .dev = dev_hdl, \ - .flags = 0, \ - } - -/** - * @brief Create musical buzzer instance based on RMT driver - * - * @param config musical buzzer configuration - * @param[out] ret_handle returned handle of musical buzzer instance - * @return - * - ESP_OK: create musical buzzer instance successfully - * - ESP_ERR_INVALID_ARG: wrong parameter - * - ESP_ERR_NO_MEM: no memory to allocate instance - */ -esp_err_t musical_buzzer_create_rmt(const musical_buzzer_config_t *config, musical_buzzer_t **ret_handle); - -#ifdef __cplusplus -} -#endif diff --git a/examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/src/musical_buzzer_rmt.c b/examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/src/musical_buzzer_rmt.c deleted file mode 100644 index e85e350ba6..0000000000 --- a/examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/src/musical_buzzer_rmt.c +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright 2020 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 -#include -#include -#include "esp_log.h" -#include "esp_attr.h" -#include "esp_compiler.h" -#include "driver/rmt.h" -#include "musical_buzzer.h" - -static const char *TAG = "buzzer_rmt"; - -#define BUZZER_CHECK(a, msg, tag, ret, ...) \ - do { \ - if (unlikely(!(a))) { \ - ESP_LOGE(TAG, "%s(%d): " msg, __FUNCTION__, __LINE__, ##__VA_ARGS__); \ - ret_code = ret; \ - goto tag; \ - } \ - } while (0) - -typedef struct { - musical_buzzer_t parent; - rmt_channel_t channel; - uint32_t counter_clk_hz; - const musical_buzzer_notation_t *notation; - uint32_t notation_length; - uint32_t next_notation_index; -} rmt_buzzer_t; - -static IRAM_ATTR rmt_item32_t update_notation_freq_duration(rmt_buzzer_t *rmt_buzzer) -{ - rmt_item32_t notation_code = {.level0 = 1, .duration0 = 1, .level1 = 0, .duration1 = 1}; - const musical_buzzer_notation_t *notation = &rmt_buzzer->notation[rmt_buzzer->next_notation_index]; - - // convert frequency to RMT item format - notation_code.duration0 = rmt_buzzer->counter_clk_hz / notation->note_freq_hz / 2; - notation_code.duration1 = notation_code.duration0; - // convert duration to RMT loop count - rmt_set_tx_loop_count(rmt_buzzer->channel, notation->note_duration_ms * notation->note_freq_hz / 1000); - - rmt_buzzer->next_notation_index++; - return notation_code; -} - -static esp_err_t buzzer_play(musical_buzzer_t *buzzer, const musical_buzzer_notation_t *notation, uint32_t notation_length) -{ - esp_err_t ret_code = ESP_OK; - rmt_buzzer_t *rmt_buzzer = __containerof(buzzer, rmt_buzzer_t, parent); - - BUZZER_CHECK(notation, "notation can't be null", err, ESP_ERR_INVALID_ARG); - - // update notation with the new one - rmt_buzzer->notation = notation; - rmt_buzzer->next_notation_index = 0; - rmt_buzzer->notation_length = notation_length; - - rmt_item32_t notation_code = update_notation_freq_duration(rmt_buzzer); - // start TX - rmt_write_items(rmt_buzzer->channel, ¬ation_code, 1, false); -err: - return ret_code; -} - -static esp_err_t buzzer_stop(musical_buzzer_t *buzzer) -{ - rmt_buzzer_t *rmt_buzzer = __containerof(buzzer, rmt_buzzer_t, parent); - rmt_tx_stop(rmt_buzzer->channel); - return ESP_OK; -} - -static esp_err_t buzzer_del(musical_buzzer_t *buzzer) -{ - rmt_buzzer_t *rmt_buzzer = __containerof(buzzer, rmt_buzzer_t, parent); - free(rmt_buzzer); - return ESP_OK; -} - -static void rmt_tx_loop_end(rmt_channel_t channel, void *args) -{ - rmt_buzzer_t *rmt_buzzer = (rmt_buzzer_t *)args; - - // stop it firstly, RMT TX engine won't stop automatically in loop mode - rmt_tx_stop(rmt_buzzer->channel); - - // update rmt loop freq and duration if the notation doesn't reach the end - if (rmt_buzzer->next_notation_index < rmt_buzzer->notation_length) { - rmt_item32_t notation_code = update_notation_freq_duration(rmt_buzzer); - // issue a new TX transaction - rmt_write_items(rmt_buzzer->channel, ¬ation_code, 1, false); - } -} - -esp_err_t musical_buzzer_create_rmt(const musical_buzzer_config_t *config, musical_buzzer_t **ret_handle) -{ - esp_err_t ret_code = ESP_OK; - rmt_buzzer_t *rmt_buzzer = NULL; - BUZZER_CHECK(config, "configuration can't be null", err, ESP_ERR_INVALID_ARG); - BUZZER_CHECK(ret_handle, "can't assign handle to null", err, ESP_ERR_INVALID_ARG); - - rmt_buzzer = calloc(1, sizeof(rmt_buzzer_t)); - BUZZER_CHECK(rmt_buzzer, "allocate context memory failed", err, ESP_ERR_NO_MEM); - - rmt_buzzer->channel = (rmt_channel_t)config->dev; - - rmt_get_counter_clock(rmt_buzzer->channel, &rmt_buzzer->counter_clk_hz); - - // register tx end callback function, which got invoked when tx loop comes to the end - rmt_register_tx_end_callback(rmt_tx_loop_end, rmt_buzzer); - - rmt_buzzer->parent.del = buzzer_del; - rmt_buzzer->parent.play = buzzer_play; - rmt_buzzer->parent.stop = buzzer_stop; - - *ret_handle = &(rmt_buzzer->parent); - return ESP_OK; - -err: - if (rmt_buzzer) { - free(rmt_buzzer); - } - return ret_code; -} diff --git a/examples/peripherals/rmt/musical_buzzer/main/CMakeLists.txt b/examples/peripherals/rmt/musical_buzzer/main/CMakeLists.txt index 34c61acc67..6f8ea967c3 100644 --- a/examples/peripherals/rmt/musical_buzzer/main/CMakeLists.txt +++ b/examples/peripherals/rmt/musical_buzzer/main/CMakeLists.txt @@ -1,3 +1,2 @@ -idf_component_register(SRCS "musical_buzzer_example_main.c" - PRIV_REQUIRES musical_buzzer driver +idf_component_register(SRCS "musical_buzzer_example_main.c" "musical_score_encoder.c" INCLUDE_DIRS ".") diff --git a/examples/peripherals/rmt/musical_buzzer/main/musical_buzzer_example_main.c b/examples/peripherals/rmt/musical_buzzer/main/musical_buzzer_example_main.c index b77d5079e0..d79889b6f6 100644 --- a/examples/peripherals/rmt/musical_buzzer/main/musical_buzzer_example_main.c +++ b/examples/peripherals/rmt/musical_buzzer/main/musical_buzzer_example_main.c @@ -1,25 +1,22 @@ -/* RMT example -- Musical Buzzer +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ - This example code is in the Public Domain (or CC0 licensed, at your option.) - - Unless required by applicable law or agreed to in writing, this - software is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR - CONDITIONS OF ANY KIND, either express or implied. -*/ #include "esp_log.h" -#include "driver/rmt.h" -#include "musical_buzzer.h" +#include "driver/rmt_tx.h" +#include "musical_score_encoder.h" + +#define RMT_BUZZER_RESOLUTION_HZ 1000000 // 1MHz resolution +#define RMT_BUZZER_GPIO_NUM 0 static const char *TAG = "example"; -#define RMT_TX_CHANNEL RMT_CHANNEL_0 -#define RMT_TX_GPIO_NUM (4) - /** - * @brief Musical Notation: Beethoven's Ode to joy - * + * @brief Musical Score: Beethoven's Ode to joy */ -static const musical_buzzer_notation_t notation[] = { +static const buzzer_musical_score_t score[] = { {740, 400}, {740, 600}, {784, 400}, {880, 400}, {880, 400}, {784, 400}, {740, 400}, {659, 400}, {587, 400}, {587, 400}, {659, 400}, {740, 400}, @@ -43,21 +40,32 @@ static const musical_buzzer_notation_t notation[] = { void app_main(void) { - // Apply default RMT configuration - rmt_config_t dev_config = RMT_DEFAULT_CONFIG_TX(RMT_TX_GPIO_NUM, RMT_TX_CHANNEL); - dev_config.tx_config.loop_en = true; // Enable loop mode + ESP_LOGI(TAG, "Create RMT TX channel"); + rmt_channel_handle_t buzzer_chan = NULL; + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, // select source clock + .gpio_num = RMT_BUZZER_GPIO_NUM, + .mem_block_symbols = 64, + .resolution_hz = RMT_BUZZER_RESOLUTION_HZ, + .trans_queue_depth = 10, // set the maximum number of transactions that can pend in the background + }; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &buzzer_chan)); - // Install RMT driver - ESP_ERROR_CHECK(rmt_config(&dev_config)); - ESP_ERROR_CHECK(rmt_driver_install(RMT_TX_CHANNEL, 0, 0)); + ESP_LOGI(TAG, "Install musical score encoder"); + rmt_encoder_handle_t score_encoder = NULL; + musical_score_encoder_config_t encoder_config = { + .resolution = RMT_BUZZER_RESOLUTION_HZ + }; + ESP_ERROR_CHECK(rmt_new_musical_score_encoder(&encoder_config, &score_encoder)); - // This example take the RMT channel number as the device handle - musical_buzzer_config_t buzzer_config = MUSICAL_BUZZER_DEFAULT_CONFIG((musical_buzzer_dev_t)RMT_TX_CHANNEL); - musical_buzzer_t *buzzer = NULL; - // Install buzzer driver - ESP_ERROR_CHECK(musical_buzzer_create_rmt(&buzzer_config, &buzzer)); + ESP_LOGI(TAG, "Enable RMT TX channel"); + ESP_ERROR_CHECK(rmt_enable(buzzer_chan)); + ESP_LOGI(TAG, "Playing Beethoven's Ode to joy..."); - ESP_LOGI(TAG, "Playing Beethoven's Ode to joy"); - - ESP_ERROR_CHECK(buzzer->play(buzzer, notation, sizeof(notation) / sizeof(notation[0]))); + for (size_t i = 0; i < sizeof(score) / sizeof(score[0]); i++) { + rmt_transmit_config_t tx_config = { + .loop_count = score[i].duration_ms * score[i].freq_hz / 1000, + }; + ESP_ERROR_CHECK(rmt_transmit(buzzer_chan, score_encoder, &score[i], sizeof(buzzer_musical_score_t), &tx_config)); + } } diff --git a/examples/peripherals/rmt/musical_buzzer/main/musical_score_encoder.c b/examples/peripherals/rmt/musical_buzzer/main/musical_score_encoder.c new file mode 100644 index 0000000000..a8f7574c0a --- /dev/null +++ b/examples/peripherals/rmt/musical_buzzer/main/musical_score_encoder.c @@ -0,0 +1,74 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_check.h" +#include "musical_score_encoder.h" + +static const char *TAG = "score_encoder"; + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *copy_encoder; + uint32_t resolution; +} rmt_musical_score_encoder_t; + +static size_t rmt_encode_musical_score(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_musical_score_encoder_t *score_encoder = __containerof(encoder, rmt_musical_score_encoder_t, base); + rmt_encoder_handle_t copy_encoder = score_encoder->copy_encoder; + rmt_encode_state_t session_state = 0; + buzzer_musical_score_t *score = (buzzer_musical_score_t *)primary_data; + uint32_t rmt_raw_symbol_duration = score_encoder->resolution / score->freq_hz / 2; + rmt_symbol_word_t musical_score_rmt_symbol = { + .level0 = 0, + .duration0 = rmt_raw_symbol_duration, + .level1 = 1, + .duration1 = rmt_raw_symbol_duration, + }; + size_t encoded_symbols = copy_encoder->encode(copy_encoder, channel, &musical_score_rmt_symbol, sizeof(musical_score_rmt_symbol), &session_state); + *ret_state = session_state; + return encoded_symbols; +} + +static esp_err_t rmt_del_musical_score_encoder(rmt_encoder_t *encoder) +{ + rmt_musical_score_encoder_t *score_encoder = __containerof(encoder, rmt_musical_score_encoder_t, base); + rmt_del_encoder(score_encoder->copy_encoder); + free(score_encoder); + return ESP_OK; +} + +static esp_err_t rmt_musical_score_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_musical_score_encoder_t *score_encoder = __containerof(encoder, rmt_musical_score_encoder_t, base); + rmt_encoder_reset(score_encoder->copy_encoder); + return ESP_OK; +} + +esp_err_t rmt_new_musical_score_encoder(const musical_score_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_musical_score_encoder_t *score_encoder = NULL; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + score_encoder = calloc(1, sizeof(rmt_musical_score_encoder_t)); + ESP_GOTO_ON_FALSE(score_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for musical score encoder"); + score_encoder->base.encode = rmt_encode_musical_score; + score_encoder->base.del = rmt_del_musical_score_encoder; + score_encoder->base.reset = rmt_musical_score_encoder_reset; + score_encoder->resolution = config->resolution; + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &score_encoder->copy_encoder), err, TAG, "create copy encoder failed"); + *ret_encoder = &score_encoder->base; + return ESP_OK; +err: + if (score_encoder) { + if (score_encoder->copy_encoder) { + rmt_del_encoder(score_encoder->copy_encoder); + } + free(score_encoder); + } + return ret; +} diff --git a/examples/peripherals/rmt/musical_buzzer/main/musical_score_encoder.h b/examples/peripherals/rmt/musical_buzzer/main/musical_score_encoder.h new file mode 100644 index 0000000000..26c9050ef7 --- /dev/null +++ b/examples/peripherals/rmt/musical_buzzer/main/musical_score_encoder.h @@ -0,0 +1,44 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Type of buzzer musical score + */ +typedef struct { + uint32_t freq_hz; /*!< Frequency, in Hz */ + uint32_t duration_ms; /*!< Duration, in ms */ +} buzzer_musical_score_t; + +/** + * @brief Type of musical score encoder configuration + */ +typedef struct { + uint32_t resolution; /*!< Encoder resolution, in Hz */ +} musical_score_encoder_config_t; + +/** + * @brief Create RMT encoder for encoding musical score into RMT symbols + * + * @param[in] config Encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_ERR_INVALID_ARG for any invalid arguments + * - ESP_ERR_NO_MEM out of memory when creating musical score encoder + * - ESP_OK if creating encoder successfully + */ +esp_err_t rmt_new_musical_score_encoder(const musical_score_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/rmt/musical_buzzer/pytest_musical_buzzer.py b/examples/peripherals/rmt/musical_buzzer/pytest_musical_buzzer.py new file mode 100644 index 0000000000..81e13534ac --- /dev/null +++ b/examples/peripherals/rmt/musical_buzzer/pytest_musical_buzzer.py @@ -0,0 +1,16 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +def test_musical_buzzer_example(dut: Dut) -> None: + dut.expect_exact('example: Create RMT TX channel') + dut.expect_exact('example: Install musical score encoder') + dut.expect_exact('example: Enable RMT TX channel') + dut.expect_exact("example: Playing Beethoven's Ode to joy") diff --git a/tools/ci/check_copyright_ignore.txt b/tools/ci/check_copyright_ignore.txt index 79c6888494..fb5573713b 100644 --- a/tools/ci/check_copyright_ignore.txt +++ b/tools/ci/check_copyright_ignore.txt @@ -2072,10 +2072,6 @@ examples/peripherals/mcpwm/mcpwm_brushed_dc_control/main/mcpwm_brushed_dc_contro examples/peripherals/mcpwm/mcpwm_capture_hc_sr04/main/mcpwm_capture_hc_sr04.c examples/peripherals/mcpwm/mcpwm_servo_control/main/mcpwm_servo_control_example_main.c examples/peripherals/mcpwm/mcpwm_sync_example/main/mcpwm_sync_example.c -examples/peripherals/rmt/morse_code/main/morse_code_main.c -examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/include/musical_buzzer.h -examples/peripherals/rmt/musical_buzzer/components/musical_buzzer/src/musical_buzzer_rmt.c -examples/peripherals/rmt/musical_buzzer/main/musical_buzzer_example_main.c examples/peripherals/sdio/host/main/app_main.c examples/peripherals/sdio/sdio_test.py examples/peripherals/sdio/slave/main/app_main.c From 0e19bc146314be8e8d674bf7b5a770b0336d4cbb Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 7 Apr 2022 13:14:01 +0800 Subject: [PATCH 08/10] example: update stepper motor example with new rmt driver --- examples/peripherals/rmt/step_motor/README.md | 92 ----- .../components/step_motor/CMakeLists.txt | 10 - .../step_motor/include/step_motor.h | 134 -------- .../step_motor/include/step_motor_driver_io.h | 75 ---- .../include/step_motor_driver_io_a4988.h | 49 --- .../components/step_motor/src/step_motor.c | 32 -- .../src/step_motor_driver_io_a4988.c | 174 ---------- .../step_motor/src/step_motor_rmt.c | 325 ------------------ .../rmt/step_motor/main/CMakeLists.txt | 2 - .../rmt/step_motor/main/step_motor_main.c | 92 ----- .../CMakeLists.txt | 0 .../peripherals/rmt/stepper_motor/README.md | 79 +++++ .../rmt/stepper_motor/main/CMakeLists.txt | 2 + .../main/stepper_motor_encoder.c | 181 ++++++++++ .../main/stepper_motor_encoder.h | 58 ++++ .../main/stepper_motor_example_main.c | 106 ++++++ .../rmt/stepper_motor/pytest_stepper_motor.py | 17 + 17 files changed, 443 insertions(+), 985 deletions(-) delete mode 100644 examples/peripherals/rmt/step_motor/README.md delete mode 100644 examples/peripherals/rmt/step_motor/components/step_motor/CMakeLists.txt delete mode 100644 examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor.h delete mode 100644 examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor_driver_io.h delete mode 100644 examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor_driver_io_a4988.h delete mode 100644 examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor.c delete mode 100644 examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor_driver_io_a4988.c delete mode 100644 examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor_rmt.c delete mode 100644 examples/peripherals/rmt/step_motor/main/CMakeLists.txt delete mode 100644 examples/peripherals/rmt/step_motor/main/step_motor_main.c rename examples/peripherals/rmt/{step_motor => stepper_motor}/CMakeLists.txt (100%) create mode 100644 examples/peripherals/rmt/stepper_motor/README.md create mode 100644 examples/peripherals/rmt/stepper_motor/main/CMakeLists.txt create mode 100644 examples/peripherals/rmt/stepper_motor/main/stepper_motor_encoder.c create mode 100644 examples/peripherals/rmt/stepper_motor/main/stepper_motor_encoder.h create mode 100644 examples/peripherals/rmt/stepper_motor/main/stepper_motor_example_main.c create mode 100644 examples/peripherals/rmt/stepper_motor/pytest_stepper_motor.py diff --git a/examples/peripherals/rmt/step_motor/README.md b/examples/peripherals/rmt/step_motor/README.md deleted file mode 100644 index e5c98ffccd..0000000000 --- a/examples/peripherals/rmt/step_motor/README.md +++ /dev/null @@ -1,92 +0,0 @@ -| Supported Targets | ESP32-S2 | ESP32-C3 | ESP32-S3 | -| ----------------- | -------- | -------- | -------- | - -# RMT Transmit Loop Example -- Step Motor controller - -(See the README.md file in the upper level 'examples' directory for more information about examples.) - -RMT peripheral can send customized RMT items in a loop, which means we can use it to generate a configurable length of periodic signal, with accurate number of pulses. - -This example will show how to control an A4988 based step motor driver to step accurately with simple APIs, based on the RMT loop feature. The example also implements a [Smoothstep](https://en.wikipedia.org/wiki/Smoothstep) feature which works out of the box. - -## How to Use Example - -### Hardware Required - -* Recommend running this example on development board with SOC chip that support loop auto-stop feature by hardware (e.g. ESP32-S3) -* A USB cable for Power supply and programming -* A 4-wire (A+, A-, B+, B-) step motor -* An A4988 module - -Connection : - -``` -+----------------+ +--------------------+ +--------------+ -| | | A4988 | | 4-wire | -| GND +-------------+ GND | | Step | -| | | | | Motor | -| 5V +-------------+ VDD 1B +------+ A2 | -| | | | | | -| GPIO18 +------------>+ DIRECTION 1A +------+ A1 | -| | | | | | -| ESP GPIO17 +------------>+ STEP 2A +------+ B1 | -| | | | | | -| GPIO16 +------------>+ SLEEP 2B +------+ B2 | -| | | | +--------------+ -| GPIO15 +------------>+ RESET VMOT +-------------------+ -| | | | | -| GPIO7 +------------>+ MS3 GND +----------+ | -| | | | | | -| GPIO6 +------------>+ MS2 | | | -| | | | | | -| GPIO5 +------------>+ MS1 | +---+--------+-----+ -| | | | | GND +12V | -| GPIO4 +------------>+ ENABLE | | POWER SUPPLY | -+----------------+ +--------------------+ +------------------+ - -``` - -IO mapping on ESP side can be changed in `step_motor_main.c`: - -```c -// GPIO configuration -#define STEP_MOTOR_DIRECTION_PIN GPIO_NUM_18 -#define STEP_MOTOR_STEP_PIN GPIO_NUM_17 -#define STEP_MOTOR_SLEEP_PIN GPIO_NUM_16 -#define STEP_MOTOR_RESET_PIN GPIO_NUM_15 -#define STEP_MOTOR_MS3_PIN GPIO_NUM_7 -#define STEP_MOTOR_MS2_PIN GPIO_NUM_6 -#define STEP_MOTOR_MS1_PIN GPIO_NUM_5 -#define STEP_MOTOR_ENABLE_PIN GPIO_NUM_4 -``` - -### Build and Flash - -Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. - -(To exit the serial monitor, type ``Ctrl-]``.) - -See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. - - -## Example Output - -``` -I (344) step_motor: init -I (344) step_motor: set_step -I (1354) step_motor: step 10 @ 1000/s -I (2364) step_motor: step 100 @ 1000/s -I (3464) step_motor: step 1000 @ 1200/s -I (5294) step_motor: step 5000 @ 1400/s -I (9864) step_motor: smoothstep start 5000 steps @ 500~1400/s -I (14454) step_motor: smoothstep finish -I (15454) step_motor: continuous running for 5s -I (20454) step_motor: stop -I (21504) step_motor: deinit -``` - -Motor should move as output indicates. - -## Troubleshooting - -For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/rmt/step_motor/components/step_motor/CMakeLists.txt b/examples/peripherals/rmt/step_motor/components/step_motor/CMakeLists.txt deleted file mode 100644 index 5e28a285f3..0000000000 --- a/examples/peripherals/rmt/step_motor/components/step_motor/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -set(component_srcs "src/step_motor.c" - "src/step_motor_rmt.c" - "src/step_motor_driver_io_a4988.c" -) - -idf_component_register(SRCS "${component_srcs}" - INCLUDE_DIRS "include" - PRIV_INCLUDE_DIRS "" - PRIV_REQUIRES "driver" - REQUIRES "") diff --git a/examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor.h b/examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor.h deleted file mode 100644 index d4149b45b9..0000000000 --- a/examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor.h +++ /dev/null @@ -1,134 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#pragma once - -#include -#include "driver/rmt.h" -#include "hal/rmt_types.h" -#include "esp_err.h" -#include "step_motor_driver_io.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief Type of step motor interface - */ -typedef struct step_motor_s step_motor_t; - -typedef step_motor_t *step_motor_handle_t; - -/** - * @brief Declaration of step motor interface - * - */ -struct step_motor_s { - esp_err_t (*init)(step_motor_t *handle); - esp_err_t (*deinit)(step_motor_t *handle); - esp_err_t (*step)(step_motor_t *handle, uint32_t n, uint32_t speed); - esp_err_t (*smooth_step)(step_motor_t *handle, uint32_t n, uint32_t speed_steps, uint32_t speed_min, uint32_t speed_max); - esp_err_t (*set_step)(step_motor_t *handle, uint16_t microstep, bool direction); - - // TODO: other API like sleep, enable_output, reset -}; - -/** - * @brief Initialize step motor driver - * - * @param handle driver handle - * @return - * - ESP_OK: successfully initialized - * - ESP_ERR_INVALID_ARG: wrong parameter - */ -esp_err_t step_motor_init(step_motor_t *handle); - -/** - * @brief Deinitialize driver - * - * @param handle driver handle - * @return - * - ESP_OK: Stop playing successfully - */ -esp_err_t step_motor_deinit(step_motor_t *handle); - -/** - * @brief Move n small steps. - * - * @note Will block until finish if n is finite steps. But will immediately return if n is UINT32_MAX. - * - * @param handle driver handle - * @param n step count, UINT32_MAX for unlimited, 0 to stop - * @param speed steps per second - * @return - * - ESP_OK: Recycle memory successfully - */ -esp_err_t step_motor_step(step_motor_t *handle, uint32_t n, uint32_t speed); - -/** - * @brief Move n small steps. Always blocking and take smooth arguments - * - * ^ speed (steps/s) - * | ********************* <---- speed_max - * | * | | * - * | * | | * - * | * | | * - * | * | | * - * | * speed | n-speed_steps*2 | speed * - * | * steps | | steps * <---- speed_min - * | | | - * +-------------------------------------------------------------------> timestamp (s) - * - * @param handle driver handle - * @param n steps - * @param speed_steps number of sample points during speed smoothing - * @param speed_min minimal speed, steps per seconds - * @param speed_max maximum speed, steps per seconds - * @note may consume lots of ram depending on speed_steps with current implementation (1000 will lead to 8kb of ram usage) - * @return - * - ESP_OK: Recycle memory successfully - */ -esp_err_t step_motor_smooth_step(step_motor_t *handle, uint32_t n, uint32_t speed_steps, uint32_t speed_min, uint32_t speed_max); - -/** - * @brief Set microstep resolution - * - * @param handle driver handle - * @param step_config microstep resolution - * @param direction rotating direction - * @return - * - ESP_OK: Recycle memory successfully - */ -esp_err_t step_motor_set_step(step_motor_t *handle, uint16_t microstep, bool direction); - - -// TODO: move out of this header to rmt one (like step_motor_rmt.h) -/** - * @brief Create step motor instance based on RMT driver - * - * @param[in] io_driver step motor low part driver - * @param[out] ret_handle returned handle of step motor instance - * @return - * - ESP_OK: create step motor instance successfully - * - ESP_ERR_INVALID_ARG: wrong parameter - * - ESP_ERR_NO_MEM: no memory to allocate instance - */ -esp_err_t step_motor_create_rmt(step_motor_driver_io_t *io_driver, const rmt_config_t *rmt_conf, step_motor_handle_t *ret_handle); - -/** - * @brief Delete step motor instance that previously created - * - * @param[in] handle step motor instance to be deleted - * @return - * - ESP_OK: create step motor instance successfully - * - ESP_ERR_INVALID_ARG: wrong parameter - */ -esp_err_t step_motor_delete_rmt(step_motor_handle_t handle); - -#ifdef __cplusplus -} -#endif diff --git a/examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor_driver_io.h b/examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor_driver_io.h deleted file mode 100644 index 800029f644..0000000000 --- a/examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor_driver_io.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#pragma once - -#include "esp_err.h" - -#ifdef __cplusplus -extern "C" { -#endif - -typedef struct step_motor_driver_io_s step_motor_driver_io_t; - -typedef step_motor_driver_io_t *step_motor_driver_io_handle_t; - -typedef enum { - STEP_MOTOR_DIRECTION_NEGATIVE = 0, STEP_MOTOR_DIRECTION_POSITIVE -} step_direction; - -/** - * @brief init low part of driver - * GPIO configuration, Bus initializing... - */ -typedef esp_err_t (*step_motor_driver_io_init)(step_motor_driver_io_t *handle); -/** - * @brief set rotation direction - */ -typedef esp_err_t (*step_motor_driver_io_set_direction)(step_motor_driver_io_t *handle, step_direction direction); -/** - * @brief enable/disable sleep mode if supported - */ -typedef esp_err_t (*step_motor_driver_io_enable_sleep)(step_motor_driver_io_t *handle, bool enabled); -/** - * @brief enable/disable output if supported - */ -typedef esp_err_t (*step_motor_driver_io_enable_output)(step_motor_driver_io_t *handle, bool enabled); -/** - * @brief set microstep configuration if supported. - * param microstep is treated as denominator. a input of 16 means 1/16 step - * should return ESP_ERR_NOT_SUPPORTED if not supported - */ -typedef esp_err_t (*step_motor_driver_io_set_microstep)(step_motor_driver_io_t *handle, uint16_t microstep); -/** - * @brief reset low part of driver - */ -typedef esp_err_t (*step_motor_driver_io_reset)(step_motor_driver_io_t *handle); -/** - * @brief deinit low part of driver - */ -typedef esp_err_t (*step_motor_driver_io_deinit)(step_motor_driver_io_t *handle); - -/** - * @brief Driver IC specified control logic - * - * leave callback pointer NULL if action is not supported - */ -struct step_motor_driver_io_s { - step_motor_driver_io_init init; /*!< callback to init low part driver */ - step_motor_driver_io_set_direction set_direction; /*!< callback to set rotate direction */ - step_motor_driver_io_enable_sleep enable_sleep; /*!< callback to enable sleep mode */ - step_motor_driver_io_enable_output enable_output; /*!< callback to enable output */ - step_motor_driver_io_set_microstep set_microstep; /*!< callback to set microstep configuration */ - bool step_triggered_edge; /*!< true if step is triggered by positive edge, otherwise false */ - uint32_t pulse_low_period_us; /*!< minimum low level pulse width on step pin */ - uint32_t pulse_high_period_us; /*!< minimum high level pulse width on step pin */ - step_motor_driver_io_reset trigger_reset; /*!< callback to trigger a reset on low part driver */ - step_motor_driver_io_deinit deinit; /*!< callback to deinit low part driver */ -}; - -#ifdef __cplusplus -} -#endif diff --git a/examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor_driver_io_a4988.h b/examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor_driver_io_a4988.h deleted file mode 100644 index e61cad187d..0000000000 --- a/examples/peripherals/rmt/step_motor/components/step_motor/include/step_motor_driver_io_a4988.h +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#pragma once - -#include "esp_err.h" -#include "step_motor_driver_io.h" - -#ifdef __cplusplus -extern "C" { -#endif - -/** - * @brief A4988 configuration - */ -typedef struct step_motor_io_a4988_conf_s { - gpio_num_t direction_pin; - gpio_num_t sleep_pin; - gpio_num_t reset_pin; - gpio_num_t ms3_pin; - gpio_num_t ms2_pin; - gpio_num_t ms1_pin; - gpio_num_t enable_pin; -} step_motor_io_a4988_conf_t; - -/** - * @brief A4988 low part driver handle - */ -typedef struct step_motor_driver_io_a4988_s { - step_motor_driver_io_t base; - step_motor_io_a4988_conf_t conf; -} step_motor_driver_io_a4988_t; - -/** - * @brief create an A4988 driver handle - */ -esp_err_t step_motor_new_a4988_io_driver(const step_motor_io_a4988_conf_t *conf, step_motor_driver_io_handle_t *handle); - -/** - * @brief delete an A4988 driver handle - */ -esp_err_t step_motor_delete_a4988_io_driver(step_motor_driver_io_handle_t handle); - -#ifdef __cplusplus -} -#endif diff --git a/examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor.c b/examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor.c deleted file mode 100644 index 8e9e8e7826..0000000000 --- a/examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor.c +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include "step_motor.h" - -esp_err_t step_motor_init(step_motor_t *handle) -{ - return handle->init(handle); -} - -esp_err_t step_motor_deinit(step_motor_t *handle) -{ - return handle->deinit(handle); -} - -esp_err_t step_motor_step(step_motor_t *handle, uint32_t n, uint32_t speed) -{ - return handle->step(handle, n, speed); -} - -esp_err_t step_motor_smooth_step(step_motor_t *handle, uint32_t n, uint32_t speed_steps, uint32_t speed_min, uint32_t speed_max) -{ - return handle->smooth_step(handle, n, speed_steps, speed_min, speed_max); -} - -esp_err_t step_motor_set_step(step_motor_t *handle, uint16_t microstep, bool direction) -{ - return handle->set_step(handle, microstep, direction); -} diff --git a/examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor_driver_io_a4988.c b/examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor_driver_io_a4988.c deleted file mode 100644 index e106671f1a..0000000000 --- a/examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor_driver_io_a4988.c +++ /dev/null @@ -1,174 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include -#include -#include -#include "hal/gpio_types.h" -#include "driver/gpio.h" -#include "esp_check.h" -#include "step_motor_driver_io_a4988.h" - -static const char *TAG = "A4988_IO"; - -#define A4988_RESPONSE_DELAY_MS 10 - -static esp_err_t a4988_init(step_motor_driver_io_t *handle) -{ - step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base); - gpio_config_t io_conf; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_OUTPUT; - //bit mask of the pins that you want to set,e.g.GPIO18/19 - io_conf.pin_bit_mask = BIT64(a4988_motor->conf.direction_pin) | - BIT64(a4988_motor->conf.sleep_pin) | - BIT64(a4988_motor->conf.reset_pin) | - BIT64(a4988_motor->conf.ms3_pin) | - BIT64(a4988_motor->conf.ms2_pin) | - BIT64(a4988_motor->conf.ms1_pin) | - BIT64(a4988_motor->conf.enable_pin); - io_conf.pull_down_en = 0; - io_conf.pull_up_en = 0; - ESP_ERROR_CHECK(gpio_config(&io_conf)); - - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.direction_pin, 0)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.sleep_pin, 0)); // default sleep - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.reset_pin, 0)); // keep reset - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 0)); // 1/1 phase - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 0)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 0)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.enable_pin, 1)); // disable by default - vTaskDelay(pdMS_TO_TICKS(A4988_RESPONSE_DELAY_MS)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.reset_pin, 1)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.sleep_pin, 1)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.enable_pin, 0)); - vTaskDelay(pdMS_TO_TICKS(A4988_RESPONSE_DELAY_MS)); - return ESP_OK; -} - -static esp_err_t a4988_set_direction(step_motor_driver_io_t *handle, step_direction direction) -{ - step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.direction_pin, direction)); - return ESP_OK; -} - -static esp_err_t a4988_enable_sleep(step_motor_driver_io_t *handle, bool enabled) -{ - step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.sleep_pin, enabled)); - return ESP_OK; -} - -static esp_err_t a4988_enable_output(step_motor_driver_io_t *handle, bool enabled) -{ - step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.enable_pin, enabled)); - return ESP_OK; -} - -static esp_err_t a4988_set_microstep(step_motor_driver_io_t *handle, uint16_t microstep) -{ - step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base); - switch (microstep) { - case 1: - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 0)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 0)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 0)); - break; - case 2: - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 0)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 0)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 1)); - break; - case 4: - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 0)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 1)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 0)); - break; - case 8: - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 0)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 1)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 1)); - break; - case 16: - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms3_pin, 1)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms2_pin, 1)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.ms1_pin, 1)); - break; - default: - return ESP_ERR_NOT_SUPPORTED; - } - return ESP_OK; -} - -static esp_err_t a4988_reset(step_motor_driver_io_t *handle) -{ - step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.reset_pin, 0)); - vTaskDelay(pdMS_TO_TICKS(A4988_RESPONSE_DELAY_MS)); - ESP_ERROR_CHECK(gpio_set_level(a4988_motor->conf.reset_pin, 1)); - return ESP_OK; -} - -static esp_err_t a4988_deinit(step_motor_driver_io_t *handle) -{ - step_motor_driver_io_a4988_t *a4988_motor = __containerof(handle, step_motor_driver_io_a4988_t, base); - gpio_config_t io_conf; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = BIT64(a4988_motor->conf.direction_pin) | - BIT64(a4988_motor->conf.sleep_pin) | - BIT64(a4988_motor->conf.reset_pin) | - BIT64(a4988_motor->conf.ms3_pin) | - BIT64(a4988_motor->conf.ms2_pin) | - BIT64(a4988_motor->conf.ms1_pin) | - BIT64(a4988_motor->conf.enable_pin); - io_conf.pull_down_en = 0; - io_conf.pull_up_en = 0; - ESP_ERROR_CHECK(gpio_config(&io_conf)); - return ESP_OK; -} - -esp_err_t step_motor_new_a4988_io_driver(const step_motor_io_a4988_conf_t *conf, step_motor_driver_io_handle_t *handle) -{ - esp_err_t ret = ESP_OK; - step_motor_driver_io_a4988_t *a4988 = NULL; - ESP_GOTO_ON_FALSE(conf, ESP_ERR_INVALID_ARG, err, TAG, "configuration can't be null"); - ESP_GOTO_ON_FALSE(handle, ESP_ERR_INVALID_ARG, err, TAG, "can't assign handle to null"); - - a4988 = calloc(1, sizeof(step_motor_driver_io_a4988_t)); - ESP_GOTO_ON_FALSE(a4988, ESP_ERR_NO_MEM, err, TAG, "allocate context memory failed"); - memcpy(&a4988->conf, conf, sizeof(step_motor_io_a4988_conf_t)); - - a4988->base.init = a4988_init; - a4988->base.deinit = a4988_deinit; - a4988->base.set_direction = a4988_set_direction; - a4988->base.set_microstep = a4988_set_microstep; - a4988->base.enable_sleep = a4988_enable_sleep; - a4988->base.enable_output = a4988_enable_output; - a4988->base.trigger_reset = a4988_reset; - a4988->base.step_triggered_edge = 1; - a4988->base.pulse_high_period_us = 1; - a4988->base.pulse_low_period_us = 1; - - *handle = &(a4988->base); - return ESP_OK; - -err: - if (a4988) { - free(a4988); - } - return ret; -} - -esp_err_t step_motor_delete_a4988_io_driver(step_motor_driver_io_handle_t handle) -{ - ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_STATE, TAG, "empty handle"); - step_motor_driver_io_a4988_t *a4988 = __containerof(handle, step_motor_driver_io_a4988_t, base); - free(a4988); - return ESP_OK; -} diff --git a/examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor_rmt.c b/examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor_rmt.c deleted file mode 100644 index b78a98a6ab..0000000000 --- a/examples/peripherals/rmt/step_motor/components/step_motor/src/step_motor_rmt.c +++ /dev/null @@ -1,325 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -/* - * This file contains an implementation of step motor middleware based on rmt peripheral - */ - -#include -#include "freertos/FreeRTOS.h" -#include "freertos/semphr.h" -#include "esp_log.h" -#include "esp_check.h" -#include "driver/rmt.h" -#include "step_motor.h" - -static const char *TAG = "RMT_STEP_MOTOR"; - -typedef enum { - STOPPED = 0, - SMOOTH_SPEED_UP, - SMOOTH_KEEP_SPEED, - SMOOTH_SLOW_DOWN, - UNLIMITED_LOOP, - LIMITED_LOOP, -} rmt_step_motor_running_status; - -typedef struct { - step_motor_t base; - step_motor_driver_io_t *io_driver; - rmt_channel_t rmt_ch; - rmt_step_motor_running_status status; - rmt_item32_t rmt_items_loop; - uint32_t rmt_items_loop_count; - rmt_item32_t *rmt_items_speedup; - rmt_item32_t *rmt_items_speeddown; - uint32_t rmt_items_smoothstep_count; - SemaphoreHandle_t notify_semphr; -} rmt_step_motor_t; - -static inline float helper_smootherstep_clamp(float x, float lowerlimit, float upperlimit) -{ - if (x < lowerlimit) { - x = lowerlimit; - } - if (x > upperlimit) { - x = upperlimit; - } - return x; -} - -// smoothstep formula -// see https://en.wikipedia.org/wiki/Smoothstep -static float helper_smootherstep(float edge0, float edge1, float x) -{ - // Scale, and clamp x to 0..1 range - x = helper_smootherstep_clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0); - // Evaluate polynomial - return x * x * x * (x * (x * 6 - 15) + 10) * (edge1 - edge0) + edge0; -} - -static uint16_t helper_speed_to_duration(uint16_t speed) -{ - return (uint16_t) round(1.0 * 1000 * 1000 / speed); -} - -static esp_err_t helper_fill_rmt_items(rmt_item32_t *items, uint32_t speed, const step_motor_driver_io_t *io_driver) -{ - items->duration1 = io_driver->step_triggered_edge ? io_driver->pulse_high_period_us : io_driver->pulse_low_period_us; - items->level1 = io_driver->step_triggered_edge; - items->level0 = !io_driver->step_triggered_edge; - uint32_t delay_period = helper_speed_to_duration(speed); - if (delay_period <= (io_driver->step_triggered_edge ? io_driver->pulse_low_period_us : io_driver->pulse_high_period_us)) { - ESP_LOGW(TAG, "maximum rate reached, driver will generate another possible highest rate instead"); - items->duration0 = io_driver->step_triggered_edge ? io_driver->pulse_low_period_us : io_driver->pulse_high_period_us; - } else { - items->duration0 = delay_period; - } - return ESP_OK; -} - -static esp_err_t rmt_step_motor_init(step_motor_t *motor) -{ - rmt_step_motor_t *rmt_handle = __containerof(motor, rmt_step_motor_t, base); - step_motor_driver_io_t *io_driver = rmt_handle->io_driver; - if (io_driver->init) { - return io_driver->init(io_driver); - } - return ESP_OK; -} - -static esp_err_t rmt_step_motor_deinit(step_motor_t *motor) -{ - rmt_step_motor_t *rmt_handle = __containerof(motor, rmt_step_motor_t, base); - step_motor_driver_io_t *io_driver = rmt_handle->io_driver; - if (io_driver->deinit) { - return io_driver->deinit(io_driver); - } - return ESP_OK; -} - -// assume n != 0 and speed is within considerable range -static esp_err_t rmt_step_motor_step_impl(step_motor_t *motor, uint32_t n, uint32_t speed) -{ - rmt_step_motor_t *rmt_handle = __containerof(motor, rmt_step_motor_t, base); - - ESP_ERROR_CHECK(rmt_set_tx_loop_mode(rmt_handle->rmt_ch, true)); - ESP_ERROR_CHECK(rmt_enable_tx_loop_autostop(rmt_handle->rmt_ch, true)); - - rmt_handle->rmt_items_loop_count = n; - if ((rmt_handle->rmt_items_loop_count) > 1023) { - (rmt_handle->rmt_items_loop_count) -= 1023; - ESP_ERROR_CHECK(rmt_set_tx_loop_count(rmt_handle->rmt_ch, 1023)); - } else { - ESP_ERROR_CHECK(rmt_set_tx_loop_count(rmt_handle->rmt_ch, rmt_handle->rmt_items_loop_count)); - rmt_handle->rmt_items_loop_count = 0; - } - helper_fill_rmt_items(&rmt_handle->rmt_items_loop, speed, rmt_handle->io_driver); - - rmt_handle->status = LIMITED_LOOP; - - rmt_write_items(rmt_handle->rmt_ch, &rmt_handle->rmt_items_loop, 1, false); - xSemaphoreTake(rmt_handle->notify_semphr, portMAX_DELAY); - return ESP_OK; -} - -static esp_err_t rmt_step_motor_step(step_motor_t *handle, uint32_t n, uint32_t speed) -{ - rmt_step_motor_t *rmt_handle = __containerof(handle, rmt_step_motor_t, base); - - ESP_ERROR_CHECK(rmt_tx_stop(rmt_handle->rmt_ch)); - - if (n == UINT32_MAX) { // forever loop, non-blocking - ESP_ERROR_CHECK(rmt_set_tx_loop_count(rmt_handle->rmt_ch, 0)); - ESP_ERROR_CHECK(rmt_enable_tx_loop_autostop(rmt_handle->rmt_ch, false)); - ESP_ERROR_CHECK(rmt_set_tx_loop_mode(rmt_handle->rmt_ch, true)); - helper_fill_rmt_items(&rmt_handle->rmt_items_loop, speed, rmt_handle->io_driver); - rmt_handle->status = UNLIMITED_LOOP; - ESP_ERROR_CHECK(rmt_write_items(rmt_handle->rmt_ch, &rmt_handle->rmt_items_loop, 1, false)); - return ESP_OK; - } else if (n == 0) { // break the forever loop - rmt_handle->status = STOPPED; - ESP_ERROR_CHECK(rmt_tx_stop(rmt_handle->rmt_ch)); - ESP_ERROR_CHECK(rmt_set_tx_loop_mode(rmt_handle->rmt_ch, false)); - return ESP_OK; - } else { // normally move n steps - ESP_RETURN_ON_FALSE(helper_speed_to_duration(speed) > 1, ESP_ERR_INVALID_ARG, TAG, - "speed too fast"); - return rmt_step_motor_step_impl(handle, n, speed); - } -} - -static esp_err_t rmt_step_motor_smoothstep(step_motor_t *handle, uint32_t n, uint32_t speed_steps, uint32_t speed_min, - uint32_t speed_max) -{ - esp_err_t ret = ESP_OK; - ESP_RETURN_ON_FALSE(speed_min <= speed_max, ESP_ERR_INVALID_ARG, TAG, "max speed lower than min speed"); - ESP_RETURN_ON_FALSE(n > speed_steps * 2, ESP_ERR_INVALID_ARG, TAG, "too few steps. consider lower speed_steps"); - ESP_RETURN_ON_FALSE(helper_speed_to_duration(speed_min) < 1 << 15, ESP_ERR_INVALID_ARG, TAG, "min speed too low"); - ESP_RETURN_ON_FALSE(helper_speed_to_duration(speed_max) > 1, ESP_ERR_INVALID_ARG, TAG, "max speed too high"); - - rmt_step_motor_t *rmt_handle = __containerof(handle, rmt_step_motor_t, base); - rmt_handle->rmt_items_speedup = malloc(sizeof(rmt_item32_t) * speed_steps); - ESP_RETURN_ON_FALSE(rmt_handle->rmt_items_speedup != NULL, ESP_ERR_NO_MEM, TAG, - "failed to allocate rmt_items_speedup"); - rmt_handle->rmt_items_speeddown = malloc(sizeof(rmt_item32_t) * speed_steps); - ESP_GOTO_ON_FALSE(rmt_handle->rmt_items_speeddown != NULL, ESP_ERR_NO_MEM, err_free_speedup, TAG, - "failed to allocate rmt_items_speeddown"); - ESP_GOTO_ON_ERROR(rmt_tx_stop(rmt_handle->rmt_ch), err_free_speeddown, TAG, "failed to stop rmt tx"); - - // prepare speed tables - for (int i = 0; i < speed_steps; ++i) { - helper_fill_rmt_items(&rmt_handle->rmt_items_speedup[i], - (uint16_t)helper_smootherstep( - (float)speed_min, - (float)speed_max, - (float)speed_min + ( (float)i / (float)speed_steps) * (float)(speed_max - speed_min)) - , rmt_handle->io_driver - ); - } - for (int i = 0; i < speed_steps; ++i) { - helper_fill_rmt_items(&rmt_handle->rmt_items_speeddown[i], - speed_max + speed_min - (uint16_t)helper_smootherstep( - (float)speed_min, - (float)speed_max, - (float)speed_min + ((float) i / (float)speed_steps) * (float)(speed_max - speed_min) - ) - , rmt_handle->io_driver - ); - } - rmt_handle->rmt_items_smoothstep_count = speed_steps; - // prepare continuous phase rmt payload - helper_fill_rmt_items(&rmt_handle->rmt_items_loop, speed_max, rmt_handle->io_driver); - rmt_handle->rmt_items_loop_count = n - speed_steps * 2; - // set status to be checked inside ISR - rmt_handle->status = SMOOTH_SPEED_UP; - // start transmitting - ESP_ERROR_CHECK(rmt_write_items(rmt_handle->rmt_ch, rmt_handle->rmt_items_speedup, speed_steps, false)); - - // waiting for transfer done - xSemaphoreTake(rmt_handle->notify_semphr, portMAX_DELAY); - -err_free_speeddown: - free(rmt_handle->rmt_items_speeddown); -err_free_speedup: - free(rmt_handle->rmt_items_speedup); - return ret; -} - -static esp_err_t rmt_step_motor_set_step(step_motor_t *handle, uint16_t microstep, bool direction) -{ - rmt_step_motor_t *rmt_handle = __containerof(handle, rmt_step_motor_t, base); - step_motor_driver_io_t *io_driver = rmt_handle->io_driver; - if (io_driver->set_direction) { - ESP_ERROR_CHECK(io_driver->set_direction(io_driver, direction)); - } - if (io_driver->set_microstep) { - ESP_ERROR_CHECK(io_driver->set_microstep(io_driver, microstep)); - } - // at least 200ns delay as described in datasheet - esp_rom_delay_us(1); - return ESP_OK; -} - -static IRAM_ATTR void rmt_tx_loop_intr(rmt_channel_t channel, void *args) -{ - rmt_step_motor_t *rmt_step_motor = (rmt_step_motor_t *) args; - - // smoothstep speedup stage finished - if (rmt_step_motor->status == SMOOTH_SPEED_UP) { - rmt_step_motor->status = SMOOTH_KEEP_SPEED; - rmt_set_tx_loop_mode(rmt_step_motor->rmt_ch, true); - rmt_enable_tx_loop_autostop(rmt_step_motor->rmt_ch, true); - rmt_set_tx_intr_en(rmt_step_motor->rmt_ch, 0); - // continue and configure loop count - } - - if (rmt_step_motor->status == SMOOTH_KEEP_SPEED || rmt_step_motor->status == LIMITED_LOOP) { - // loop count not 0, continuing looping - if ((rmt_step_motor->rmt_items_loop_count) != 0) { - if ((rmt_step_motor->rmt_items_loop_count) > 1023) { - (rmt_step_motor->rmt_items_loop_count) -= 1023; - rmt_set_tx_loop_count(rmt_step_motor->rmt_ch, 1023); - } else { - rmt_set_tx_loop_count(rmt_step_motor->rmt_ch, rmt_step_motor->rmt_items_loop_count); - rmt_step_motor->rmt_items_loop_count = 0; - } - rmt_write_items(rmt_step_motor->rmt_ch, &rmt_step_motor->rmt_items_loop, 1, false); - return; - } - } - - // smoothstep keep speed stage finished - if (rmt_step_motor->status == SMOOTH_KEEP_SPEED) { - rmt_step_motor->status = SMOOTH_SLOW_DOWN; - rmt_set_tx_loop_mode(rmt_step_motor->rmt_ch, false); - rmt_enable_tx_loop_autostop(rmt_step_motor->rmt_ch, false); - rmt_set_tx_intr_en(rmt_step_motor->rmt_ch, 1); - rmt_write_items(rmt_step_motor->rmt_ch, rmt_step_motor->rmt_items_speeddown, rmt_step_motor->rmt_items_smoothstep_count, false); - return; - } - - if (rmt_step_motor->status == LIMITED_LOOP || rmt_step_motor->status == SMOOTH_SLOW_DOWN) { - rmt_step_motor->status = STOPPED; - BaseType_t xHigherPriorityTaskWoken = pdFALSE; - xSemaphoreGiveFromISR(rmt_step_motor->notify_semphr, &xHigherPriorityTaskWoken); - if (xHigherPriorityTaskWoken == pdTRUE) { - portYIELD_FROM_ISR(); - } - } -} - -esp_err_t step_motor_create_rmt(step_motor_driver_io_t *io_driver, const rmt_config_t *rmt_conf, step_motor_handle_t *ret_handle) -{ - esp_err_t ret = ESP_OK; - rmt_step_motor_t *rmt_step_motor = NULL; - - ESP_RETURN_ON_ERROR(rmt_config(rmt_conf), TAG, "Failed to configure RMT"); - ESP_RETURN_ON_ERROR(rmt_driver_install(rmt_conf->channel, 0, 0), TAG, "Failed to install RMT driver"); - - ESP_GOTO_ON_FALSE(io_driver, ESP_ERR_INVALID_ARG, err, TAG, "configuration can't be null"); - ESP_GOTO_ON_FALSE(ret_handle, ESP_ERR_INVALID_ARG, err, TAG, "can't assign handle to null"); - - rmt_step_motor = calloc(1, sizeof(rmt_step_motor_t)); - ESP_GOTO_ON_FALSE(rmt_step_motor, ESP_ERR_NO_MEM, err, TAG, "allocate context memory failed"); - rmt_step_motor->rmt_ch = rmt_conf->channel; - - rmt_step_motor->notify_semphr = xSemaphoreCreateBinary(); - ESP_GOTO_ON_FALSE(rmt_step_motor, ESP_ERR_NO_MEM, err, TAG, "allocate semaphore memory failed"); - - rmt_step_motor->io_driver = io_driver; - - // register tx end callback function, which got invoked when tx loop comes to the end - rmt_register_tx_end_callback(rmt_tx_loop_intr, rmt_step_motor); - - rmt_step_motor->base.init = rmt_step_motor_init; - rmt_step_motor->base.deinit = rmt_step_motor_deinit; - rmt_step_motor->base.step = rmt_step_motor_step; - rmt_step_motor->base.set_step = rmt_step_motor_set_step; - rmt_step_motor->base.smooth_step = rmt_step_motor_smoothstep; - - *ret_handle = &(rmt_step_motor->base); - return ESP_OK; - -err: - if (rmt_step_motor) { - if (rmt_step_motor->notify_semphr) { - vSemaphoreDelete(rmt_step_motor->notify_semphr); - } - free(rmt_step_motor); - } - return ret; -} - -esp_err_t step_motor_delete_rmt(step_motor_handle_t handle) -{ - ESP_RETURN_ON_FALSE(handle, ESP_ERR_INVALID_STATE, TAG, "empty handle"); - rmt_step_motor_t *rmt_handle = __containerof(handle, rmt_step_motor_t, base); - ESP_RETURN_ON_ERROR(rmt_driver_uninstall(rmt_handle->rmt_ch), TAG, "Failed to uninstall RMT driver"); - vSemaphoreDelete(rmt_handle->notify_semphr); - free(rmt_handle); - return ESP_OK; -} diff --git a/examples/peripherals/rmt/step_motor/main/CMakeLists.txt b/examples/peripherals/rmt/step_motor/main/CMakeLists.txt deleted file mode 100644 index 1cb079a34f..0000000000 --- a/examples/peripherals/rmt/step_motor/main/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -idf_component_register(SRCS "step_motor_main.c" - INCLUDE_DIRS ".") diff --git a/examples/peripherals/rmt/step_motor/main/step_motor_main.c b/examples/peripherals/rmt/step_motor/main/step_motor_main.c deleted file mode 100644 index 2c18716529..0000000000 --- a/examples/peripherals/rmt/step_motor/main/step_motor_main.c +++ /dev/null @@ -1,92 +0,0 @@ -/* RMT example -- step motor */ - -/* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD - * - * SPDX-License-Identifier: CC0-1.0 - */ - -#include "sdkconfig.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_log.h" -#include "driver/rmt.h" -#include "step_motor.h" -#include "step_motor_driver_io_a4988.h" - -// GPIO configuration -#define STEP_MOTOR_DIRECTION_PIN GPIO_NUM_18 -#define STEP_MOTOR_STEP_PIN GPIO_NUM_17 -#define STEP_MOTOR_SLEEP_PIN GPIO_NUM_16 -#define STEP_MOTOR_RESET_PIN GPIO_NUM_15 -#define STEP_MOTOR_MS3_PIN GPIO_NUM_7 -#define STEP_MOTOR_MS2_PIN GPIO_NUM_6 -#define STEP_MOTOR_MS1_PIN GPIO_NUM_5 -#define STEP_MOTOR_ENABLE_PIN GPIO_NUM_4 - -#define RMT_TX_CHANNEL RMT_CHANNEL_0 - -static const char *TAG = "step_motor"; - -void app_main(void) -{ - // Apply default RMT configuration - rmt_config_t dev_config = RMT_DEFAULT_CONFIG_TX(STEP_MOTOR_STEP_PIN, RMT_TX_CHANNEL); - - step_motor_io_a4988_conf_t a4988_conf = { - .direction_pin = STEP_MOTOR_DIRECTION_PIN, - .sleep_pin = STEP_MOTOR_SLEEP_PIN, - .reset_pin = STEP_MOTOR_RESET_PIN, - .ms3_pin = STEP_MOTOR_MS3_PIN, - .ms2_pin = STEP_MOTOR_MS2_PIN, - .ms1_pin = STEP_MOTOR_MS1_PIN, - .enable_pin = STEP_MOTOR_ENABLE_PIN, - }; - - // Install low part driver - step_motor_driver_io_t *a4988_io; - ESP_ERROR_CHECK(step_motor_new_a4988_io_driver(&a4988_conf, &a4988_io)); - - // Install rmt driver - step_motor_t *motor = NULL; - ESP_ERROR_CHECK(step_motor_create_rmt(a4988_io, &dev_config, &motor)); - - step_motor_init(motor); - ESP_LOGI(TAG, "init"); - - ESP_LOGI(TAG, "set_step"); - // configure Microstep to Full Step - step_motor_set_step(motor, 1, STEP_MOTOR_DIRECTION_POSITIVE); - vTaskDelay(pdMS_TO_TICKS(1000)); - - ESP_LOGI(TAG, "step 10 @ 1000/s"); - step_motor_step(motor, 10, 1000); - vTaskDelay(pdMS_TO_TICKS(1000)); - ESP_LOGI(TAG, "step 100 @ 1000/s"); - step_motor_step(motor, 100, 1000); - vTaskDelay(pdMS_TO_TICKS(1000)); - ESP_LOGI(TAG, "step 1000 @ 1200/s"); - step_motor_step(motor, 1000, 1200); - vTaskDelay(pdMS_TO_TICKS(1000)); - ESP_LOGI(TAG, "step 5000 @ 1400/s"); - step_motor_step(motor, 5000, 1400); - vTaskDelay(pdMS_TO_TICKS(1000)); - - ESP_LOGI(TAG, "smoothstep start 5000 steps @ 500~1400/s"); - step_motor_smooth_step(motor, 5000, 1000, 500, 1400); - ESP_LOGI(TAG, "smoothstep finish"); - vTaskDelay(pdMS_TO_TICKS(1000)); - - ESP_LOGI(TAG, "continuous running for 5s"); - step_motor_step(motor, UINT32_MAX, 1000); - vTaskDelay(pdMS_TO_TICKS(5000)); - ESP_LOGI(TAG, "stop"); - step_motor_step(motor, 0, 1000); - - vTaskDelay(pdMS_TO_TICKS(1000)); - step_motor_deinit(motor); - ESP_LOGI(TAG, "deinit"); - ESP_ERROR_CHECK(step_motor_delete_rmt(motor)); - - ESP_ERROR_CHECK(step_motor_delete_a4988_io_driver(a4988_io)); -} diff --git a/examples/peripherals/rmt/step_motor/CMakeLists.txt b/examples/peripherals/rmt/stepper_motor/CMakeLists.txt similarity index 100% rename from examples/peripherals/rmt/step_motor/CMakeLists.txt rename to examples/peripherals/rmt/stepper_motor/CMakeLists.txt diff --git a/examples/peripherals/rmt/stepper_motor/README.md b/examples/peripherals/rmt/stepper_motor/README.md new file mode 100644 index 0000000000..cc66f2500a --- /dev/null +++ b/examples/peripherals/rmt/stepper_motor/README.md @@ -0,0 +1,79 @@ +| Supported Targets | ESP32-S3 | +| ----------------- | -------- | + +# RMT Based Stepper Motor Smooth Controller + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +One RMT TX channel can use different encoders in sequence, which is useful to generate waveforms that have obvious multiple stages. + +This example shows how to drive a stepper motor with a **STEP/DIR** interfaced controller (e.g. [DRV8825](https://www.ti.com/lit/ds/symlink/drv8825.pdf)) in a [smooth](https://en.wikipedia.org/wiki/Smoothstep) way. To smoothly drive a stepper motor, there're three phases: **Acceleration**, **Uniform** and **Deceleration**. Accordingly, this example implements two encoders so that RMT channel can generate the waveforms with different characteristics: + +* `curve_encoder` is to encode the **Acceleration** and **Deceleration** phase +* `uniform_encoder` is to encode the ***Uniform** phase + +## How to Use Example + +### Hardware Required + +* A development board with any supported Espressif SOC chip (see `Supported Targets` table above) +* A USB cable for Power supply and programming +* A two-phase four-wire stepper motor +* A DRV8825 stepper motor controller + +Connection : + +``` ++---------------------------+ +--------------------+ +--------------+ +| ESP Board | | DRV8825 | | 4-wire | +| GND +-------------+ GND | | Step | +| | | | | Motor | +| 3V3 +-------------+ VDD A+ +------+ A+ | +| | | | | | +| STEP_MOTOR_GPIO_DIR +------------>+ DIR A- +------+ A- | +| | | | | | +| STEP_MOTOR_GPIO_STEP +------------>+ STEP B- +------+ B- | +| | | | | | +| | 3V3----+ nSLEEP B+ +------+ B+ | +| | | | +--------------+ +| | 3V3----+ nRST VM +-------------------+ +| | | | | +| | 3V3|GND----+ M2 GND +----------+ | +| | | | | | +| | 3V3|GND----+ M1 | | | +| | | | | | +| | 3V3|GND----+ M0 | +---+--------+-----+ +| | | | | GND +12V | +| STEP_MOTOR_GPIO_EN +------------>+ nEN | | POWER SUPPLY | ++---------------------------+ +--------------------+ +------------------+ +``` + +The GPIO number used in this example can be changed according to your board, by the macro `STEP_MOTOR_GPIO_EN`, `STEP_MOTOR_GPIO_DIR` and `STEP_MOTOR_GPIO_STEP` defined in the [source file](main/stepper_motor_example_main.c). + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + + +## Example Output + +``` +I (0) cpu_start: Starting scheduler on APP CPU. +I (325) example: Initialize EN + DIR GPIO +I (325) gpio: GPIO[16]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (335) gpio: GPIO[17]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 0| Pulldown: 0| Intr:0 +I (345) example: Create RMT TX channel +I (365) example: Set spin direction +I (365) example: Enable step motor +I (375) example: Create motor encoders +I (405) example: Start RMT channel +I (405) example: Spin motor for 6000 steps: 500 accel + 5000 uniform + 500 decel +``` + +## Troubleshooting + +For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/rmt/stepper_motor/main/CMakeLists.txt b/examples/peripherals/rmt/stepper_motor/main/CMakeLists.txt new file mode 100644 index 0000000000..ca5fa8da19 --- /dev/null +++ b/examples/peripherals/rmt/stepper_motor/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "stepper_motor_example_main.c" "stepper_motor_encoder.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/rmt/stepper_motor/main/stepper_motor_encoder.c b/examples/peripherals/rmt/stepper_motor/main/stepper_motor_encoder.c new file mode 100644 index 0000000000..b125b59a31 --- /dev/null +++ b/examples/peripherals/rmt/stepper_motor/main/stepper_motor_encoder.c @@ -0,0 +1,181 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_check.h" +#include "stepper_motor_encoder.h" + +static const char *TAG = "stepper_motor_encoder"; + +static float convert_to_smooth_freq(uint32_t freq1, uint32_t freq2, uint32_t freqx) +{ + float normalize_x = ((float)(freqx - freq1)) / (freq2 - freq1); + // third-order "smoothstep" function: https://en.wikipedia.org/wiki/Smoothstep + float smooth_x = normalize_x * normalize_x * (3 - 2 * normalize_x); + return smooth_x * (freq2 - freq1) + freq1; +} + +typedef struct { + rmt_encoder_t base; + rmt_encoder_handle_t copy_encoder; + uint32_t sample_points; + struct { + uint32_t is_accel_curve: 1; + } flags; + rmt_symbol_word_t curve_table[]; +} rmt_stepper_curve_encoder_t; + +static size_t rmt_encode_stepper_motor_curve(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_stepper_curve_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_curve_encoder_t, base); + rmt_encoder_handle_t copy_encoder = motor_encoder->copy_encoder; + rmt_encode_state_t session_state = 0; + uint32_t points_num = *(uint32_t *)primary_data; + size_t encoded_symbols = 0; + if (motor_encoder->flags.is_accel_curve) { + encoded_symbols = copy_encoder->encode(copy_encoder, channel, &motor_encoder->curve_table[0], + points_num * sizeof(rmt_symbol_word_t), &session_state); + } else { + encoded_symbols = copy_encoder->encode(copy_encoder, channel, &motor_encoder->curve_table[0] + motor_encoder->sample_points - points_num, + points_num * sizeof(rmt_symbol_word_t), &session_state); + } + *ret_state = session_state; + return encoded_symbols; +} + +static esp_err_t rmt_del_stepper_motor_curve_encoder(rmt_encoder_t *encoder) +{ + rmt_stepper_curve_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_curve_encoder_t, base); + rmt_del_encoder(motor_encoder->copy_encoder); + free(motor_encoder); + return ESP_OK; +} + +static esp_err_t rmt_reset_stepper_motor_curve_encoder(rmt_encoder_t *encoder) +{ + rmt_stepper_curve_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_curve_encoder_t, base); + rmt_encoder_reset(motor_encoder->copy_encoder); + return ESP_OK; +} + +esp_err_t rmt_new_stepper_motor_curve_encoder(const stepper_motor_curve_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_stepper_curve_encoder_t *step_encoder = NULL; + float smooth_freq; + uint32_t symbol_duration; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid arguments"); + ESP_GOTO_ON_FALSE(config->sample_points, ESP_ERR_INVALID_ARG, err, TAG, "sample points number can't be zero"); + ESP_GOTO_ON_FALSE(config->start_freq_hz != config->end_freq_hz, ESP_ERR_INVALID_ARG, err, TAG, "start freq can't equal to end freq"); + step_encoder = calloc(1, sizeof(rmt_stepper_curve_encoder_t) + config->sample_points * sizeof(rmt_symbol_word_t)); + ESP_GOTO_ON_FALSE(step_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for stepper curve encoder"); + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &step_encoder->copy_encoder), err, TAG, "create copy encoder failed"); + bool is_accel_curve = config->start_freq_hz < config->end_freq_hz; + + // prepare the curve table, in RMT symbol format + if (is_accel_curve) { + uint32_t curve_step = (config->end_freq_hz - config->start_freq_hz) / (config->sample_points - 1); + for (uint32_t i = 0; i < config->sample_points; i++) { + smooth_freq = convert_to_smooth_freq(config->start_freq_hz, config->end_freq_hz, config->start_freq_hz + curve_step * i); + symbol_duration = config->resolution / smooth_freq / 2; + step_encoder->curve_table[i].level0 = 0; + step_encoder->curve_table[i].duration0 = symbol_duration; + step_encoder->curve_table[i].level1 = 1; + step_encoder->curve_table[i].duration1 = symbol_duration; + } + } else { + uint32_t curve_step = (config->start_freq_hz - config->end_freq_hz) / (config->sample_points - 1); + for (uint32_t i = 0; i < config->sample_points; i++) { + smooth_freq = convert_to_smooth_freq(config->end_freq_hz, config->start_freq_hz, config->end_freq_hz + curve_step * i); + symbol_duration = config->resolution / smooth_freq / 2; + step_encoder->curve_table[config->sample_points - i - 1].level0 = 0; + step_encoder->curve_table[config->sample_points - i - 1].duration0 = symbol_duration; + step_encoder->curve_table[config->sample_points - i - 1].level1 = 1; + step_encoder->curve_table[config->sample_points - i - 1].duration1 = symbol_duration; + } + } + + step_encoder->sample_points = config->sample_points; + step_encoder->flags.is_accel_curve = is_accel_curve; + step_encoder->base.del = rmt_del_stepper_motor_curve_encoder; + step_encoder->base.encode = rmt_encode_stepper_motor_curve; + step_encoder->base.reset = rmt_reset_stepper_motor_curve_encoder; + *ret_encoder = &(step_encoder->base); + return ESP_OK; +err: + if (step_encoder) { + if (step_encoder->copy_encoder) { + rmt_del_encoder(step_encoder->copy_encoder); + } + free(step_encoder); + } + return ret; +} + +typedef struct { + rmt_encoder_t base; + rmt_encoder_handle_t copy_encoder; + uint32_t resolution; +} rmt_stepper_uniform_encoder_t; + +static size_t rmt_encode_stepper_motor_uniform(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_stepper_uniform_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_uniform_encoder_t, base); + rmt_encoder_handle_t copy_encoder = motor_encoder->copy_encoder; + rmt_encode_state_t session_state = 0; + uint32_t target_freq_hz = *(uint32_t *)primary_data; + uint32_t symbol_duration = motor_encoder->resolution / target_freq_hz / 2; + rmt_symbol_word_t freq_sample = { + .level0 = 0, + .duration0 = symbol_duration, + .level1 = 1, + .duration1 = symbol_duration, + }; + size_t encoded_symbols = copy_encoder->encode(copy_encoder, channel, &freq_sample, sizeof(freq_sample), &session_state); + *ret_state = session_state; + return encoded_symbols; +} + +static esp_err_t rmt_del_stepper_motor_uniform_encoder(rmt_encoder_t *encoder) +{ + rmt_stepper_uniform_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_uniform_encoder_t, base); + rmt_del_encoder(motor_encoder->copy_encoder); + free(motor_encoder); + return ESP_OK; +} + +static esp_err_t rmt_reset_stepper_motor_uniform(rmt_encoder_t *encoder) +{ + rmt_stepper_uniform_encoder_t *motor_encoder = __containerof(encoder, rmt_stepper_uniform_encoder_t, base); + rmt_encoder_reset(motor_encoder->copy_encoder); + return ESP_OK; +} + +esp_err_t rmt_new_stepper_motor_uniform_encoder(const stepper_motor_uniform_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_stepper_uniform_encoder_t *step_encoder = NULL; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid arguments"); + step_encoder = calloc(1, sizeof(rmt_stepper_uniform_encoder_t)); + ESP_GOTO_ON_FALSE(step_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for stepper uniform encoder"); + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &step_encoder->copy_encoder), err, TAG, "create copy encoder failed"); + + step_encoder->resolution = config->resolution; + step_encoder->base.del = rmt_del_stepper_motor_uniform_encoder; + step_encoder->base.encode = rmt_encode_stepper_motor_uniform; + step_encoder->base.reset = rmt_reset_stepper_motor_uniform; + *ret_encoder = &(step_encoder->base); + return ESP_OK; +err: + if (step_encoder) { + if (step_encoder->copy_encoder) { + rmt_del_encoder(step_encoder->copy_encoder); + } + free(step_encoder); + } + return ret; +} diff --git a/examples/peripherals/rmt/stepper_motor/main/stepper_motor_encoder.h b/examples/peripherals/rmt/stepper_motor/main/stepper_motor_encoder.h new file mode 100644 index 0000000000..915d6b5e6f --- /dev/null +++ b/examples/peripherals/rmt/stepper_motor/main/stepper_motor_encoder.h @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Stepper motor curve encoder configuration + */ +typedef struct { + uint32_t resolution; // Encoder resolution, in Hz + uint32_t sample_points; // Sample points used for deceleration phase + uint32_t start_freq_hz; // Start frequency on the curve, in Hz + uint32_t end_freq_hz; // End frequency on the curve, in Hz +} stepper_motor_curve_encoder_config_t; + +/** + * @brief Stepper motor uniform encoder configuration + */ +typedef struct { + uint32_t resolution; // Encoder resolution, in Hz +} stepper_motor_uniform_encoder_config_t; + +/** + * @brief Create stepper motor curve encoder + * + * @param[in] config Encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_ERR_INVALID_ARG for any invalid arguments + * - ESP_ERR_NO_MEM out of memory when creating step motor encoder + * - ESP_OK if creating encoder successfully + */ +esp_err_t rmt_new_stepper_motor_curve_encoder(const stepper_motor_curve_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +/** + * @brief Create RMT encoder for encoding step motor uniform phase into RMT symbols + * + * @param[in] config Encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_ERR_INVALID_ARG for any invalid arguments + * - ESP_ERR_NO_MEM out of memory when creating step motor encoder + * - ESP_OK if creating encoder successfully + */ +esp_err_t rmt_new_stepper_motor_uniform_encoder(const stepper_motor_uniform_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/rmt/stepper_motor/main/stepper_motor_example_main.c b/examples/peripherals/rmt/stepper_motor/main/stepper_motor_example_main.c new file mode 100644 index 0000000000..f25051ba2e --- /dev/null +++ b/examples/peripherals/rmt/stepper_motor/main/stepper_motor_example_main.c @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "driver/rmt_tx.h" +#include "driver/gpio.h" +#include "esp_log.h" +#include "stepper_motor_encoder.h" + +///////////////////////////////Change the following configurations according to your board////////////////////////////// +#define STEP_MOTOR_GPIO_EN 16 +#define STEP_MOTOR_GPIO_DIR 17 +#define STEP_MOTOR_GPIO_STEP 18 +#define STEP_MOTOR_ENABLE_LEVEL 0 // DRV8825 is enabled on low level +#define STEP_MOTOR_SPIN_DIR_CLOCKWISE 0 +#define STEP_MOTOR_SPIN_DIR_COUNTERCLOCKWISE !STEP_MOTOR_SPIN_DIR_CLOCKWISE + +#define STEP_MOTOR_RESOLUTION_HZ 1000000 // 1MHz resolution + +static const char *TAG = "example"; + +void app_main(void) +{ + ESP_LOGI(TAG, "Initialize EN + DIR GPIO"); + gpio_config_t en_dir_gpio_config = { + .mode = GPIO_MODE_OUTPUT, + .intr_type = GPIO_INTR_DISABLE, + .pin_bit_mask = 1ULL << STEP_MOTOR_GPIO_DIR | 1ULL << STEP_MOTOR_GPIO_EN, + }; + ESP_ERROR_CHECK(gpio_config(&en_dir_gpio_config)); + + ESP_LOGI(TAG, "Create RMT TX channel"); + rmt_channel_handle_t motor_chan = NULL; + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, // select clock source + .gpio_num = STEP_MOTOR_GPIO_STEP, + .mem_block_symbols = 64, + .resolution_hz = STEP_MOTOR_RESOLUTION_HZ, + .trans_queue_depth = 10, // set the number of transactions that can be pending in the background + }; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &motor_chan)); + + ESP_LOGI(TAG, "Set spin direction"); + gpio_set_level(STEP_MOTOR_GPIO_DIR, STEP_MOTOR_SPIN_DIR_CLOCKWISE); + ESP_LOGI(TAG, "Enable step motor"); + gpio_set_level(STEP_MOTOR_GPIO_EN, STEP_MOTOR_ENABLE_LEVEL); + + ESP_LOGI(TAG, "Create motor encoders"); + stepper_motor_curve_encoder_config_t accel_encoder_config = { + .resolution = STEP_MOTOR_RESOLUTION_HZ, + .sample_points = 500, + .start_freq_hz = 500, + .end_freq_hz = 1500, + }; + rmt_encoder_handle_t accel_motor_encoder = NULL; + ESP_ERROR_CHECK(rmt_new_stepper_motor_curve_encoder(&accel_encoder_config, &accel_motor_encoder)); + + stepper_motor_uniform_encoder_config_t uniform_encoder_config = { + .resolution = STEP_MOTOR_RESOLUTION_HZ, + }; + rmt_encoder_handle_t uniform_motor_encoder = NULL; + ESP_ERROR_CHECK(rmt_new_stepper_motor_uniform_encoder(&uniform_encoder_config, &uniform_motor_encoder)); + + stepper_motor_curve_encoder_config_t decel_encoder_config = { + .resolution = STEP_MOTOR_RESOLUTION_HZ, + .sample_points = 500, + .start_freq_hz = 1500, + .end_freq_hz = 500, + }; + rmt_encoder_handle_t decel_motor_encoder = NULL; + ESP_ERROR_CHECK(rmt_new_stepper_motor_curve_encoder(&decel_encoder_config, &decel_motor_encoder)); + + ESP_LOGI(TAG, "Enable RMT channel"); + ESP_ERROR_CHECK(rmt_enable(motor_chan)); + + ESP_LOGI(TAG, "Spin motor for 6000 steps: 500 accel + 5000 uniform + 500 decel"); + rmt_transmit_config_t tx_config = { + .loop_count = 0, + }; + + const static uint32_t accel_samples = 500; + const static uint32_t uniform_speed_hz = 1500; + const static uint32_t decel_samples = 500; + + while (1) { + // acceleration phase + tx_config.loop_count = 0; + ESP_ERROR_CHECK(rmt_transmit(motor_chan, accel_motor_encoder, &accel_samples, sizeof(accel_samples), &tx_config)); + + // uniform phase + tx_config.loop_count = 5000; + ESP_ERROR_CHECK(rmt_transmit(motor_chan, uniform_motor_encoder, &uniform_speed_hz, sizeof(uniform_speed_hz), &tx_config)); + + // deceleration phase + tx_config.loop_count = 0; + ESP_ERROR_CHECK(rmt_transmit(motor_chan, decel_motor_encoder, &decel_samples, sizeof(decel_samples), &tx_config)); + // wait all transactions finished + ESP_ERROR_CHECK(rmt_tx_wait_all_done(motor_chan, -1)); + + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} diff --git a/examples/peripherals/rmt/stepper_motor/pytest_stepper_motor.py b/examples/peripherals/rmt/stepper_motor/pytest_stepper_motor.py new file mode 100644 index 0000000000..d290fb17b9 --- /dev/null +++ b/examples/peripherals/rmt/stepper_motor/pytest_stepper_motor.py @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32s3 +@pytest.mark.generic +def test_ir_nec_example(dut: Dut) -> None: + dut.expect_exact('example: Initialize EN + DIR GPIO') + dut.expect_exact('example: Create RMT TX channel') + dut.expect_exact('example: Set spin direction') + dut.expect_exact('example: Enable step motor') + dut.expect_exact('example: Create motor encoders') + dut.expect_exact('example: Enable RMT channel') + dut.expect_exact('example: Spin motor for 6000 steps: 500 accel + 5000 uniform + 500 decel') From f472551910d9c0350a44a822450a75c75b432c51 Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 7 Apr 2022 13:14:25 +0800 Subject: [PATCH 09/10] example: added dshot esc example based on new rmt driver --- .../peripherals/rmt/dshot_esc/CMakeLists.txt | 6 + examples/peripherals/rmt/dshot_esc/README.md | 60 +++++++ .../rmt/dshot_esc/main/CMakeLists.txt | 2 + .../rmt/dshot_esc/main/dshot_esc_encoder.c | 165 ++++++++++++++++++ .../rmt/dshot_esc/main/dshot_esc_encoder.h | 47 +++++ .../dshot_esc/main/dshot_esc_example_main.c | 66 +++++++ .../rmt/dshot_esc/pytest_dshot_esc.py | 18 ++ 7 files changed, 364 insertions(+) create mode 100644 examples/peripherals/rmt/dshot_esc/CMakeLists.txt create mode 100644 examples/peripherals/rmt/dshot_esc/README.md create mode 100644 examples/peripherals/rmt/dshot_esc/main/CMakeLists.txt create mode 100644 examples/peripherals/rmt/dshot_esc/main/dshot_esc_encoder.c create mode 100644 examples/peripherals/rmt/dshot_esc/main/dshot_esc_encoder.h create mode 100644 examples/peripherals/rmt/dshot_esc/main/dshot_esc_example_main.c create mode 100644 examples/peripherals/rmt/dshot_esc/pytest_dshot_esc.py diff --git a/examples/peripherals/rmt/dshot_esc/CMakeLists.txt b/examples/peripherals/rmt/dshot_esc/CMakeLists.txt new file mode 100644 index 0000000000..4f6b985554 --- /dev/null +++ b/examples/peripherals/rmt/dshot_esc/CMakeLists.txt @@ -0,0 +1,6 @@ +# The following lines of boilerplate have to be in your project's CMakeLists +# in this exact order for cmake to work correctly +cmake_minimum_required(VERSION 3.5) + +include($ENV{IDF_PATH}/tools/cmake/project.cmake) +project(dshot_esc) diff --git a/examples/peripherals/rmt/dshot_esc/README.md b/examples/peripherals/rmt/dshot_esc/README.md new file mode 100644 index 0000000000..c79399f5d7 --- /dev/null +++ b/examples/peripherals/rmt/dshot_esc/README.md @@ -0,0 +1,60 @@ +| Supported Targets | ESP32 | ESP32-S2 | ESP32-C3 | ESP32-S3 | +| ----------------- | ----- | -------- | -------- | -------- | +# RMT Infinite Loop Transmit Example -- Dshot ESC (Electronic Speed Controller) + +(See the README.md file in the upper level 'examples' directory for more information about examples.) + +RMT TX channel can transmit symbols in an infinite loop, where the loop is totally controlled by the hardware. This feature is useful for scenarios where a device needs continuous stimulus. + +The [DShot](https://github.com/betaflight/betaflight/wiki/Dshot) is a digital protocol between flight controller (FC) and ESC, which is more resistant to electrical noise than traditional analog protocols. The DShot protocol requires the FC to encode throttle information into pulses with various durations and send out the pulses periodically. This is what an RMT TX channel can perfectly do. + +## How to Use Example + +### Hardware Required + +* A development board with any supported Espressif SOC chip (see `Supported Targets` table above) +* A USB cable for Power supply and programming +* An ESC that supports DShot protocol (this example will take the **DShot300** as an example) + +Connection : + +``` + BLDC DShot ESC 12V GND ++--------+ +---------------+ | | ESP +| | | | | | +----------------------+ +| U +-----+ U P+ +----+ | | | +| | | | | | | +| V +-----+ V P- +--------+ | | +| | | | | | +| W +-----+ W SIG +----------+ DSHOT_ESC_GPIO_NUM | +| | | GND +----------+ GND | ++--------+ +---------------+ +----------------------+ +``` + +The GPIO number used in this example can be changed according to your board, by the macro `DSHOT_ESC_GPIO_NUM` defined in the [source file](main/dshot_esc_example_main.c). + +### Build and Flash + +Run `idf.py -p PORT flash monitor` to build, flash and monitor the project. + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the [Getting Started Guide](https://docs.espressif.com/projects/esp-idf/en/latest/get-started/index.html) for full steps to configure and use ESP-IDF to build projects. + +## Console Output + +``` +I (0) cpu_start: Starting scheduler on APP CPU. +I (182) example: Create RMT TX channel +I (182) gpio: GPIO[43]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (182) example: Install Dshot ESC encoder +I (182) example: Start RMT TX channel +I (182) example: Start ESC by sending zero throttle for a while... +I (3182) example: Set throttle to 1000, no telemetry +``` + +The BLDC motor will beep when the ESC receives a burst of initialization pulses. And then starts high-speed rotation at the throttle set in the code. + +## Troubleshooting + +For any technical queries, please open an [issue] (https://github.com/espressif/esp-idf/issues) on GitHub. We will get back to you soon. diff --git a/examples/peripherals/rmt/dshot_esc/main/CMakeLists.txt b/examples/peripherals/rmt/dshot_esc/main/CMakeLists.txt new file mode 100644 index 0000000000..0e27b9096b --- /dev/null +++ b/examples/peripherals/rmt/dshot_esc/main/CMakeLists.txt @@ -0,0 +1,2 @@ +idf_component_register(SRCS "dshot_esc_example_main.c" "dshot_esc_encoder.c" + INCLUDE_DIRS ".") diff --git a/examples/peripherals/rmt/dshot_esc/main/dshot_esc_encoder.c b/examples/peripherals/rmt/dshot_esc/main/dshot_esc_encoder.c new file mode 100644 index 0000000000..3f46ea7486 --- /dev/null +++ b/examples/peripherals/rmt/dshot_esc/main/dshot_esc_encoder.c @@ -0,0 +1,165 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "esp_check.h" +#include "dshot_esc_encoder.h" + +static const char *TAG = "dshot_encoder"; + +/** + * @brief Type of Dshot ESC frame + */ +typedef union { + struct { + uint16_t crc: 4; /*!< CRC checksum */ + uint16_t telemetry: 1; /*!< Telemetry request */ + uint16_t throttle: 11; /*!< Throttle value */ + }; + uint16_t val; +} dshot_esc_frame_t; + +#ifndef __cplusplus +_Static_assert(sizeof(dshot_esc_frame_t) == 0x02, "Invalid size of dshot_esc_frame_t structure"); +#endif + +typedef struct { + rmt_encoder_t base; + rmt_encoder_t *bytes_encoder; + rmt_encoder_t *copy_encoder; + rmt_symbol_word_t dshot_delay_symbol; + int state; +} rmt_dshot_esc_encoder_t; + +static void make_dshot_frame(dshot_esc_frame_t *frame, uint16_t throttle, bool telemetry) +{ + frame->throttle = throttle; + frame->telemetry = telemetry; + uint16_t val = frame->val; + uint8_t crc = ((val ^ (val >> 4) ^ (val >> 8)) & 0xF0) >> 4;; + frame->crc = crc; + val = frame->val; + // change the endian + frame->val = ((val & 0xFF) << 8) | ((val & 0xFF00) >> 8); +} + +static size_t rmt_encode_dshot_esc(rmt_encoder_t *encoder, rmt_channel_handle_t channel, + const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) +{ + rmt_dshot_esc_encoder_t *dshot_encoder = __containerof(encoder, rmt_dshot_esc_encoder_t, base); + rmt_encoder_handle_t bytes_encoder = dshot_encoder->bytes_encoder; + rmt_encoder_handle_t copy_encoder = dshot_encoder->copy_encoder; + rmt_encode_state_t session_state = 0; + rmt_encode_state_t state = 0; + size_t encoded_symbols = 0; + + // convert user data into dshot frame + dshot_esc_throttle_t *throttle = (dshot_esc_throttle_t *)primary_data; + dshot_esc_frame_t frame = {}; + make_dshot_frame(&frame, throttle->throttle, throttle->telemetry_req); + + switch (dshot_encoder->state) { + case 0: // send the dshot frame + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &frame, sizeof(frame), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + dshot_encoder->state = 1; // switch to next state when current encoding session finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + // fall-through + case 1: + encoded_symbols += copy_encoder->encode(copy_encoder, channel, &dshot_encoder->dshot_delay_symbol, + sizeof(rmt_symbol_word_t), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + state |= RMT_ENCODING_COMPLETE; + dshot_encoder->state = 0; // switch to next state when current encoding session finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space for encoding artifacts + } + } +out: + *ret_state = state; + return encoded_symbols; +} + +static esp_err_t rmt_del_dshot_encoder(rmt_encoder_t *encoder) +{ + rmt_dshot_esc_encoder_t *dshot_encoder = __containerof(encoder, rmt_dshot_esc_encoder_t, base); + rmt_del_encoder(dshot_encoder->bytes_encoder); + rmt_del_encoder(dshot_encoder->copy_encoder); + free(dshot_encoder); + return ESP_OK; +} + +static esp_err_t rmt_dshot_encoder_reset(rmt_encoder_t *encoder) +{ + rmt_dshot_esc_encoder_t *dshot_encoder = __containerof(encoder, rmt_dshot_esc_encoder_t, base); + rmt_encoder_reset(dshot_encoder->bytes_encoder); + rmt_encoder_reset(dshot_encoder->copy_encoder); + dshot_encoder->state = 0; + return ESP_OK; +} + +esp_err_t rmt_new_dshot_esc_encoder(const dshot_esc_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) +{ + esp_err_t ret = ESP_OK; + rmt_dshot_esc_encoder_t *dshot_encoder = NULL; + ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); + dshot_encoder = calloc(1, sizeof(rmt_dshot_esc_encoder_t)); + ESP_GOTO_ON_FALSE(dshot_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for musical score encoder"); + dshot_encoder->base.encode = rmt_encode_dshot_esc; + dshot_encoder->base.del = rmt_del_dshot_encoder; + dshot_encoder->base.reset = rmt_dshot_encoder_reset; + uint32_t delay_ticks = config->resolution / 1e6 * config->post_delay_us; + rmt_symbol_word_t dshot_delay_symbol = { + .level0 = 0, + .duration0 = delay_ticks / 2, + .level1 = 0, + .duration1 = delay_ticks / 2, + }; + dshot_encoder->dshot_delay_symbol = dshot_delay_symbol; + // different dshot protocol have its own timing requirements, + float period_ticks = (float)config->resolution / config->baud_rate; + // 1 and 0 is represented by a 74.850% and 37.425% duty cycle respectively + unsigned int t1h_ticks = (unsigned int)(period_ticks * 0.7485); + unsigned int t1l_ticks = (unsigned int)(period_ticks - t1h_ticks); + unsigned int t0h_ticks = (unsigned int)(period_ticks * 0.37425); + unsigned int t0l_ticks = (unsigned int)(period_ticks - t0h_ticks); + rmt_bytes_encoder_config_t bytes_encoder_config = { + .bit0 = { + .level0 = 1, + .duration0 = t0h_ticks, + .level1 = 0, + .duration1 = t0l_ticks, + }, + .bit1 = { + .level0 = 1, + .duration0 = t1h_ticks, + .level1 = 0, + .duration1 = t1l_ticks, + }, + .flags.msb_first = 1, + }; + ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &dshot_encoder->bytes_encoder), err, TAG, "create bytes encoder failed"); + rmt_copy_encoder_config_t copy_encoder_config = {}; + ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &dshot_encoder->copy_encoder), err, TAG, "create copy encoder failed"); + *ret_encoder = &dshot_encoder->base; + return ESP_OK; +err: + if (dshot_encoder) { + if (dshot_encoder->bytes_encoder) { + rmt_del_encoder(dshot_encoder->bytes_encoder); + } + if (dshot_encoder->copy_encoder) { + rmt_del_encoder(dshot_encoder->copy_encoder); + } + free(dshot_encoder); + } + return ret; +} diff --git a/examples/peripherals/rmt/dshot_esc/main/dshot_esc_encoder.h b/examples/peripherals/rmt/dshot_esc/main/dshot_esc_encoder.h new file mode 100644 index 0000000000..8484864762 --- /dev/null +++ b/examples/peripherals/rmt/dshot_esc/main/dshot_esc_encoder.h @@ -0,0 +1,47 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ +#pragma once + +#include +#include +#include "driver/rmt_encoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Throttle representation in DShot protocol + */ +typedef struct { + uint16_t throttle; /*!< Throttle value */ + bool telemetry_req; /*!< Telemetry request */ +} dshot_esc_throttle_t; + +/** + * @brief Type of Dshot ESC encoder configuration + */ +typedef struct { + uint32_t resolution; /*!< Encoder resolution, in Hz */ + uint32_t baud_rate; /*!< Dshot protocol runs at several different baud rates, e.g. DSHOT300 = 300k baud rate */ + uint32_t post_delay_us; /*!< Delay time after one Dshot frame, in microseconds */ +} dshot_esc_encoder_config_t; + +/** + * @brief Create RMT encoder for encoding Dshot ESC frame into RMT symbols + * + * @param[in] config Encoder configuration + * @param[out] ret_encoder Returned encoder handle + * @return + * - ESP_ERR_INVALID_ARG for any invalid arguments + * - ESP_ERR_NO_MEM out of memory when creating Dshot ESC encoder + * - ESP_OK if creating encoder successfully + */ +esp_err_t rmt_new_dshot_esc_encoder(const dshot_esc_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); + +#ifdef __cplusplus +} +#endif diff --git a/examples/peripherals/rmt/dshot_esc/main/dshot_esc_example_main.c b/examples/peripherals/rmt/dshot_esc/main/dshot_esc_example_main.c new file mode 100644 index 0000000000..7277cca447 --- /dev/null +++ b/examples/peripherals/rmt/dshot_esc/main/dshot_esc_example_main.c @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Unlicense OR CC0-1.0 + */ + +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" +#include "esp_log.h" +#include "driver/rmt_tx.h" +#include "dshot_esc_encoder.h" + +#define DSHOT_ESC_RESOLUTION_HZ 40000000 // 40MHz resolution, DSHot protocol needs a relative high resolution +#define DSHOT_ESC_GPIO_NUM 0 + +static const char *TAG = "example"; + +void app_main(void) +{ + ESP_LOGI(TAG, "Create RMT TX channel"); + rmt_channel_handle_t esc_chan = NULL; + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, // select a clock that can provide needed resolution + .gpio_num = DSHOT_ESC_GPIO_NUM, + .mem_block_symbols = 64, + .resolution_hz = DSHOT_ESC_RESOLUTION_HZ, + .trans_queue_depth = 10, // set the number of transactions that can be pending in the background + }; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &esc_chan)); + + ESP_LOGI(TAG, "Install Dshot ESC encoder"); + rmt_encoder_handle_t dshot_encoder = NULL; + dshot_esc_encoder_config_t encoder_config = { + .resolution = DSHOT_ESC_RESOLUTION_HZ, + .baud_rate = 300000, // DSHOT300 protocol + .post_delay_us = 50, // extra delay between each frame + }; + ESP_ERROR_CHECK(rmt_new_dshot_esc_encoder(&encoder_config, &dshot_encoder)); + + ESP_LOGI(TAG, "Enable RMT TX channel"); + ESP_ERROR_CHECK(rmt_enable(esc_chan)); + + rmt_transmit_config_t tx_config = { + .loop_count = -1, // infinite loop + }; + dshot_esc_throttle_t throttle = { + .throttle = 0, + .telemetry_req = false, // telemetry is not supported in this example + }; + + ESP_LOGI(TAG, "Start ESC by sending zero throttle for a while..."); + ESP_ERROR_CHECK(rmt_transmit(esc_chan, dshot_encoder, &throttle, sizeof(throttle), &tx_config)); + vTaskDelay(pdMS_TO_TICKS(5000)); + + ESP_LOGI(TAG, "Increase throttle, no telemetry"); + for (uint16_t thro = 100; thro < 1000; thro += 10) { + throttle.throttle = thro; + ESP_ERROR_CHECK(rmt_transmit(esc_chan, dshot_encoder, &throttle, sizeof(throttle), &tx_config)); + // the previous loop transfer is till undergoing, we need to stop it and restart, + // so that the new throttle can be updated on the output + ESP_ERROR_CHECK(rmt_disable(esc_chan)); + ESP_ERROR_CHECK(rmt_enable(esc_chan)); + vTaskDelay(pdMS_TO_TICKS(1000)); + } + +} diff --git a/examples/peripherals/rmt/dshot_esc/pytest_dshot_esc.py b/examples/peripherals/rmt/dshot_esc/pytest_dshot_esc.py new file mode 100644 index 0000000000..a5344b4637 --- /dev/null +++ b/examples/peripherals/rmt/dshot_esc/pytest_dshot_esc.py @@ -0,0 +1,18 @@ +# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD +# SPDX-License-Identifier: CC0-1.0 + +import pytest +from pytest_embedded import Dut + + +@pytest.mark.esp32 +@pytest.mark.esp32s2 +@pytest.mark.esp32s3 +@pytest.mark.esp32c3 +@pytest.mark.generic +def test_dshot_esc_example(dut: Dut) -> None: + dut.expect_exact('example: Create RMT TX channel') + dut.expect_exact('example: Install Dshot ESC encoder') + dut.expect_exact('example: Enable RMT TX channel') + dut.expect_exact('example: Start ESC by sending zero throttle for a while...') + dut.expect_exact('example: Increase throttle, no telemetry') From a7f6f8677aad7feb6981b8369eb36f444667704d Mon Sep 17 00:00:00 2001 From: morris Date: Thu, 7 Apr 2022 13:15:23 +0800 Subject: [PATCH 10/10] doc: update api reference for new rmt driver --- .../diagrams/rmt/rmt_encoder_chain.diag | 24 + docs/_static/diagrams/rmt/rmt_rx.diag | 45 ++ docs/_static/diagrams/rmt/rmt_symbols.diag | 16 + docs/_static/diagrams/rmt/rmt_tx.diag | 33 + docs/_static/ir_nec.png | Bin 0 -> 47876 bytes docs/_static/rmt_tx_sync.png | Bin 0 -> 16394 bytes docs/doxygen/Doxyfile | 28 +- docs/doxygen/Doxyfile_esp32 | 22 +- docs/doxygen/Doxyfile_esp32c3 | 8 +- docs/doxygen/Doxyfile_esp32h2 | 8 +- docs/doxygen/Doxyfile_esp32s2 | 28 +- docs/doxygen/Doxyfile_esp32s3 | 28 +- docs/en/api-reference/peripherals/rmt.rst | 716 ++++++++++++------ docs/en/migration-guides/peripherals.rst | 53 +- docs/sphinx-known-warnings.txt | 2 + 15 files changed, 710 insertions(+), 301 deletions(-) create mode 100644 docs/_static/diagrams/rmt/rmt_encoder_chain.diag create mode 100644 docs/_static/diagrams/rmt/rmt_rx.diag create mode 100644 docs/_static/diagrams/rmt/rmt_symbols.diag create mode 100644 docs/_static/diagrams/rmt/rmt_tx.diag create mode 100644 docs/_static/ir_nec.png create mode 100644 docs/_static/rmt_tx_sync.png diff --git a/docs/_static/diagrams/rmt/rmt_encoder_chain.diag b/docs/_static/diagrams/rmt/rmt_encoder_chain.diag new file mode 100644 index 0000000000..20b2a0e578 --- /dev/null +++ b/docs/_static/diagrams/rmt/rmt_encoder_chain.diag @@ -0,0 +1,24 @@ +blockdiag rmt_encoder_chain { + orientation = portrait; + default_fontsize = 14; + node_width = 160; + node_height = 40; + span_width = 80; + span_height = 40; + + new [label = "New", shape = "beginpoint"]; + init [label = "Init"]; + encoder_a [label = "Encoder A"]; + encoder_b [label = "Encoder B"]; + other_encoder [shape = "dots"]; + yield [label = "Yield", shape = "endpoint"]; + + new -> init [folded]; + init -> encoder_a [thick]; + encoder_a <-> yield [folded, thick, color = "green", label = "full"]; + encoder_a -> encoder_b [thick]; + encoder_b <-> yield [color = "green", thick, label = "full"]; + encoder_b -> other_encoder [style = "dotted"]; + encoder_a, encoder_b -> init [thick, color = red, label = "reset"]; + other_encoder -> init [thick, color = red, label = "finish"]; +} diff --git a/docs/_static/diagrams/rmt/rmt_rx.diag b/docs/_static/diagrams/rmt/rmt_rx.diag new file mode 100644 index 0000000000..4c19a2def4 --- /dev/null +++ b/docs/_static/diagrams/rmt/rmt_rx.diag @@ -0,0 +1,45 @@ +blockdiag rmt_rx { + node_width = 80; + node_height = 60; + default_group_color = lightgrey; + + o -> p -> a; + q -> r; + r -- s; + s -> p [folded]; + + a -> b [label=GPIO]; + b -> c -> d; + e -- f; + f -> b [folded]; + + o [style=none, label="", background="../../../_static/rmt-waveform-modulated.png"] + p [shape=endpoint, label="demod"] + q [label="Remove\nCarrier"] + r [style=none, label="", background="../../../_static/rmt-carrier.png"] + s [shape=none, label=""] + + a [style=none, label="", background="../../../_static/rmt-waveform.png"] + b [label=Filter] + c [label="Edge\nDetect"] + d [style=none, width=100, label="{11,high,7,low},\n{5,high,5,low},\n..."] + e [style=none, width=60, height=40, label="Enable\nFilter"] + f [shape=none, label=""] + + group { + label = "Optional" + q,r,o,p; + } + group { + label = "Input" + a,e; + } + group { + label = "RMT Receiver" + b,c; + } + group { + label = "Output" + d; + } +} diff --git a/docs/_static/diagrams/rmt/rmt_symbols.diag b/docs/_static/diagrams/rmt/rmt_symbols.diag new file mode 100644 index 0000000000..d85f9abbc6 --- /dev/null +++ b/docs/_static/diagrams/rmt/rmt_symbols.diag @@ -0,0 +1,16 @@ +packetdiag rmt_symbols { + colwidth = 32 + node_width = 16 + node_height = 24 + default_fontsize = 12 + + 0-14: Period (15) + 15: L + 16-30: Period (15) + 31: L + 32-95: ... [colheight=2] + 96-110: Period (15) + 111: L + 112-126: Period (15) + 127: L +} diff --git a/docs/_static/diagrams/rmt/rmt_tx.diag b/docs/_static/diagrams/rmt/rmt_tx.diag new file mode 100644 index 0000000000..b613786364 --- /dev/null +++ b/docs/_static/diagrams/rmt/rmt_tx.diag @@ -0,0 +1,33 @@ +blockdiag rmt_tx { + node_width = 80; + node_height = 60; + default_group_color = lightgrey; + + a -> b -> c -> d; + e -> f -> g -- h; + d -> o [label=GPIO]; + h -> d [folded]; + + a [style=none, width=100, label="{11,high,7,low},\n{5,high,5,low},\n..."] + b [label="Waveform\nGenerator"] + c [style=none, label="", background="../../../_static/rmt-waveform.png"] + d [shape=beginpoint, label="mod"] + e [style=none, width=60, height=40, label="Enable\nCarrier"] + f [label="Carrier\nGenerator"] + g [style=none, label="", background="../../../_static/rmt-carrier.png"] + h [shape=none] + o [style=none, label="", background="../../../_static/rmt-waveform-modulated.png"] + + group { + label = "Input" + a,e; + } + group { + label = "RMT Transmitter" + b,f,c,g,d,h; + } + group { + label = "Output" + o; + } +} diff --git a/docs/_static/ir_nec.png b/docs/_static/ir_nec.png new file mode 100644 index 0000000000000000000000000000000000000000..3a5b78d90a0857acbb5e2b9f5f9ea1760a66c185 GIT binary patch literal 47876 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%0DH>wLPubzQv6Zb1g+ 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 literal 0 HcmV?d00001 diff --git a/docs/_static/rmt_tx_sync.png b/docs/_static/rmt_tx_sync.png new file mode 100644 index 0000000000000000000000000000000000000000..c49abb8b21beb10e2b65e82132f6af405256fce8 GIT binary patch literal 16394 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 r0alI)I b -> c -> d; - e -> f -> g -- h; - d -> o [label=GPIO]; - h -> d [folded]; +The data path and control path of an RMT RX channel is illustrated in the figure below: - a [style=none, width=100, label="{11,high,7,low},\n{5,high,5,low},\n..."] - b [label="Waveform\nGenerator"] - c [style=none, label="", background="../../../_static/rmt-waveform.png"] - d [shape=beginpoint, label="mod"] - e [style=none, width=60, height=40, label="Carrier\nenable"] - f [label="Carrier\nGenerator"] - g [style=none, label="", background="../../../_static/rmt-carrier.png"] - h [shape=none] - o [style=none, label="", background="../../../_static/rmt-waveform-modulated.png"] - - group { - label = Input - a,e; - } - group { - label = "RMT Transmitter" - b,f,c,g,d,h; - } - group { - label = Output - o; - } - } - -The reverse operation is performed by the receiver, where a series of pulses is decoded into a list of values containing the pulse duration and binary level. A filter may be applied to remove high frequency noise from the input signal. - -.. blockdiag:: - :scale: 90 +.. blockdiag:: /../_static/diagrams/rmt/rmt_rx.diag :caption: RMT Receiver Overview :align: center - blockdiag rmt_rx { +The RMT receiver can sample incoming signals into RMT data format, and store the data in memory. It's feasible to tell the receiver the basic characteristics of the incoming signal, so that the signal's stop condition can be recognized, and signal glitches and noise can be filtered out. The RMT peripheral also supports demodulating the high frequency carrier from the base signal. - node_width = 80; - node_height = 60; - default_group_color = lightgrey; +Functional Overview +------------------- - a -> b [label=GPIO]; - b -> c -> d; - e -- f; - f -> b [folded]; +Description of the RMT functionality is divided into the following sections: - a [style=none, label="", background="../../../_static/rmt-waveform.png"] - b [label=Filter] - c [label="Edge\nDetect"] - d [style=none, width=100, label="{11,high,7,low},\n{5,high,5,low},\n..."] - e [style=none, width=60, height=40, label="Filter\nenable"] - f [shape=none, label=""] +- `Resource Allocation <#resource-allocation>`__ - covers how to allocate RMT channels with properly set of configurations. It also covers how to recycle the resources when they finished working. +- `Carrier Modulation and Demodulation <#carrier-modulation-and-demodulation>`__ - describes how to modulate carrier for TX channel and demodulate carrier for RX channel. +- `Register Event Callbacks <#register-event-callbacks>`__ - covers how to hook user specific code to RMT channel specific events. +- `Enable and Disable channel <#enable-and-disable-channel>`__ - shows how to enable and disable the RMT channel. +- `Initiate TX Transaction <#initiate-tx-transaction>`__ - describes the steps to initiate a transaction for TX channel. +- `Initiate RX Transaction <#initiate-rx-transaction>`__ - describes the steps to initiate a transaction for RX channel. +- `Multiple Channels Simultaneous Transmission <#multiple-channels-simultaneous-transmission>`__ - describes how to collect multiple channels into a sync group and start transaction at the same time. +- `RMT Encoder <#rmt-encoder>`__ - focuses on how to write a customized encoder in a combination way, with the help of the primitive encoders provided by the driver. +- `Power Management <#power-management>`__ - describes how different source clock will affect power consumption. +- `IRAM Safe <#iram-safe>`__ - describes tips on how to make the RMT interrupt work better along with a disabled cache. +- `Thread Safety <#thread-safety>`__ - lists which APIs are guaranteed to be thread safe by the driver. +- `Kconfig Options <#kconfig-options>`__ - lists the supported Kconfig options that can bring different effects to the driver. - group { - label = Input - a,e; - } - group { - label = "RMT Receiver" - b,c; - } - group { - label = Output - d; - } - } +Resource Allocation +^^^^^^^^^^^^^^^^^^^ -There couple of typical steps to setup and operate the RMT and they are discussed in the following sections: +Both RMT TX and RX channels are represented by :cpp:type:`rmt_channel_handle_t` in the driver. The available channels are managed in a resource pool, which will hand out a free channel on request. -1. `Configure Driver`_ -2. `Transmit Data`_ or `Receive Data`_ -3. `Change Operation Parameters`_ -4. `Use Interrupts`_ +Install RMT TX Channel +~~~~~~~~~~~~~~~~~~~~~~ -.. only:: esp32 +To install an RMT TX channel, there's a configuration structure that needs to be given in advance: :cpp:type:`rmt_tx_channel_config_t`: - The RMT has eight channels numbered from zero to seven. Each channel is able to independently transmit or receive data. They are referred to using indexes defined in structure :cpp:type:`rmt_channel_t`. +- :cpp:member:`rmt_tx_channel_config_t::gpio_num` sets the GPIO number used by the transmitter. +- :cpp:member:`rmt_tx_channel_config_t::clk_src` selects the source clock for the RMT channel. The available clocks are listed in :cpp:type:`rmt_clock_source_t`. Note that, the selected clock will also be used by other channels, which means user should ensure this configuration is same when allocating other channels, regardless of TX or RX. For the effect on power consumption of different clock source, please refer to `Power Management <#power-management>`__ section. +- :cpp:member:`rmt_tx_channel_config_t::resolution_hz` sets the resolution of the internal tick counter. The timing parameter of RMT signal is calculated based on this **tick**. +- :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols` sets the size of the dedicated memory block or DMA buffer that is used to store RMT encoding artifacts. +- :cpp:member:`rmt_tx_channel_config_t::trans_queue_depth` sets the depth of internal transaction queue, the deeper the queue, the more transactions can be prepared in the backlog. +- :cpp:member:`rmt_tx_channel_config_t::invert_out` is used to decide whether to invert the RMT signal before sending it to the GPIO pad. +- :cpp:member:`rmt_tx_channel_config_t::with_dma` is used to indicate if the channel needs a DMA backend. A channel with DMA attached can offload the CPU by a lot. However, DMA backend is not available on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you enable this option. Or you might encounter :c:macro:`ESP_ERR_NOT_SUPPORTED` error. +- :cpp:member:`rmt_tx_channel_config_t::io_loop_back` is for debugging purposes only. It enables both the GPIO's input and output ability through the GPIO matrix peripheral. Meanwhile, if both TX and RX channels are bound to the same GPIO, then monitoring of the data transmission line can be realized. -.. only:: esp32s2 +Once the :cpp:type:`rmt_tx_channel_config_t` structure is populated with mandatory parameters, users can call :cpp:func:`rmt_new_tx_channel` to allocate and initialize a TX channel. This function will return an RMT channel handle if it runs correctly. Specifically, when there are no more free channels in the RMT resource pool, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. If some feature (e.g. DMA backend) is not supported by hardware, it will return :c:macro:`ESP_ERR_NOT_SUPPORTED` error. - The RMT has four channels numbered from zero to three. Each channel is able to independently transmit or receive data. They are referred to using indexes defined in structure :cpp:type:`rmt_channel_t`. +.. code:: c -.. only:: esp32c3 + rmt_channel_handle_t tx_chan = NULL; + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, // select source clock + .gpio_num = 0, // GPIO number + .mem_block_symbols = 64, // memory block size, 64 * 4 = 256Bytes + .resolution_hz = 1 * 1000 * 1000, // 1MHz tick resolution, i.e. 1 tick = 1us + .trans_queue_depth = 4, // set the number of transactions that can pend in the background + .flags.invert_out = false, // don't invert output signal + .flags.with_dma = false, // don't need DMA backend + }; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_chan)); - The RMT has four channels numbered from zero to three. The first half (i.e. Channel 0 ~ 1) channels can only be configured for transmitting, and the other half (i.e. Channel 2 ~ 3) channels can only be configured for receiving. They are referred to using indexes defined in structure :cpp:type:`rmt_channel_t`. +Install RMT RX Channel +~~~~~~~~~~~~~~~~~~~~~~ -.. only:: esp32s3 +To install an RMT RX channel, there's a configuration structure that needs to be given in advance: :cpp:type:`rmt_rx_channel_config_t`: - The RMT has eight channels numbered from zero to seven. The first half (i.e. Channel 0 ~ 3) channels can only be configured for transmitting, and the other half (i.e. Channel 4 ~ 7) channels can only be configured for receiving. They are referred to using indexes defined in structure :cpp:type:`rmt_channel_t`. +- :cpp:member:`rmt_rx_channel_config_t::gpio_num` sets the GPIO number used by the receiver. +- :cpp:member:`rmt_rx_channel_config_t::clk_src` selects the source clock for the RMT channel. The available clocks are listed in :cpp:type:`rmt_clock_source_t`. Note that, the selected clock will also be used by other channels, which means user should ensure this configuration is same when allocating other channels, regardless of TX or RX. For the effect on power consumption of different clock source, please refer to `Power Management <#power-management>`__ section. +- :cpp:member:`rmt_rx_channel_config_t::resolution_hz` sets the resolution of the internal tick counter. The timing parameter of RMT signal is calculated based on this **tick**. +- :cpp:member:`rmt_rx_channel_config_t::mem_block_symbols` sets the size of the dedicated memory block or DMA buffer that used to store RMT encoding artifacts. +- :cpp:member:`rmt_rx_channel_config_t::invert_in` is used to decide whether to invert the input signals before they going into RMT receiver. The inversion is done by GPIO matrix instead of by the RMT peripheral. +- :cpp:member:`rmt_rx_channel_config_t::with_dma` is used to indicate if the channel needs a DMA backend. A channel with DMA attached can offload the CPU by a lot. However, DMA backend is not available on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you enable this option. Or you might encounter :c:macro:`ESP_ERR_NOT_SUPPORTED` error. +- :cpp:member:`rmt_rx_channel_config_t::io_loop_back` is for debugging purposes only. It enables both the GPIO's input and output ability through the GPIO matrix peripheral. Meanwhile, if both TX and RX channels are bound to the same GPIO, then monitoring of the data transmission line can be realized. -Configure Driver ----------------- +Once the :cpp:type:`rmt_rx_channel_config_t` structure is populated with mandatory parameters, users can call :cpp:func:`rmt_new_rx_channel` to allocate and initialize a RX channel. This function will return an RMT channel handle if it runs correctly. Specifically, when there are no more free channels in the RMT resource pool, this function will return :c:macro:`ESP_ERR_NOT_FOUND` error. If some feature (e.g. DMA backend) is not supported by hardware, it will return :c:macro:`ESP_ERR_NOT_SUPPORTED` error. -There are several parameters that define how particular channel operates. Most of these parameters are configured by setting specific members of :cpp:type:`rmt_config_t` structure. Some of the parameters are common to both transmit or receive mode, and some are mode specific. They are all discussed below. +.. code:: c + rmt_channel_handle_t rx_chan = NULL; + rmt_rx_channel_config_t rx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, // select source clock + .resolution_hz = 1 * 1000 * 1000, // 1MHz tick resolution, i.e. 1 tick = 1us + .mem_block_symbols = 64, // memory block size, 64 * 4 = 256Bytes + .gpio_num = 2, // GPIO number + .flags.invert_in = false, // don't invert input signal + .flags.with_dma = false, // don't need DMA backend + }; + ESP_ERROR_CHECK(rmt_new_rx_channel(&rx_chan_config, &rx_chan)); -Common Parameters -^^^^^^^^^^^^^^^^^ +Uninstall RMT Channel +~~~~~~~~~~~~~~~~~~~~~ -* The **channel** to be configured, select one from the :cpp:type:`rmt_channel_t` enumerator. -* The RMT **operation mode** - whether this channel is used to transmit or receive data, selected by setting a **rmt_mode** members to one of the values from :cpp:type:`rmt_mode_t`. -* What is the **pin number** to transmit or receive RMT signals, selected by setting **gpio_num**. -* How many **memory blocks** will be used by the channel, set with **mem_block_num**. -* Extra miscellaneous parameters for the channel can be set in the **flags**. +If a previously installed RMT channel is no longer needed, it's recommended to recycle the resources by calling :cpp:func:`rmt_del_channel`, which in return allows the underlying hardware to be usable for other purposes. - * When **RMT_CHANNEL_FLAGS_AWARE_DFS** is set, RMT channel will take REF_TICK or XTAL as source clock. The benefit is, RMT channel can continue work even when APB clock is changing. See :doc:`power_management <../system/power_management>` for more information. - * When **RMT_CHANNEL_FLAGS_INVERT_SIG** is set, the driver will invert the RMT signal sending to or receiving from the channel. It just works like an external inverter connected to the GPIO of certain RMT channel. +Carrier Modulation and Demodulation +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -* A **clock divider**, that will determine the range of pulse length generated by the RMT transmitter or discriminated by the receiver. Selected by setting **clk_div** to a value within [1 .. 255] range. The RMT source clock is typically APB CLK, 80Mhz by default. But when **RMT_CHANNEL_FLAGS_AWARE_DFS** is set in **flags**, RMT source clock is changed to REF_TICK or XTAL. +The RMT transmitter can generate a carrier wave and modulate it onto the base signal. Compared to the base signal, the carrier frequency is usually high. In addition, user can only set the frequency and duty cycle for the carrier. The RMT receiver can demodulate the carrier from the incoming signal. Note that, carrier modulation and demodulation is not supported on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before configuring the carrier, or you might encounter a :c:macro:`ESP_ERR_NOT_SUPPORTED` error. + +Carrier related configurations lie in :cpp:type:`rmt_carrier_config_t`: + +- :cpp:member:`rmt_carrier_config_t::frequency_hz` sets the carrier frequency, in Hz. +- :cpp:member:`rmt_carrier_config_t::duty_cycle` sets the carrier duty cycle. +- :cpp:member:`rmt_carrier_config_t::polarity_active_low` sets the carrier polarity, i.e. on which level the carrier is applied. +- :cpp:member:`rmt_carrier_config_t::always_on` sets whether to output the carrier even when the data transmission has finished. This configuration is only valid for TX channel. .. note:: - The period of a square wave after the clock divider is called a 'tick'. The length of the pulses generated by the RMT transmitter or discriminated by the receiver is configured in number of 'ticks'. + For RX channel, we shouldn't set the carrier frequency exactly to the theoretical value. It's recommended to leave a tolerance for the carrier frequency. For example, in the snippet below, we set the frequency to 25KHz, instead of the 38KHz that configured on the TX side. The reason is that reflection and refraction will occur when a signal travels through the air, leading to the a distortion on the receiver side. -There are also couple of specific parameters that should be set up depending if selected channel is configured in `Transmit Mode`_ or `Receive Mode`_: +.. code:: c + rmt_carrier_config_t tx_carrier_cfg = { + .duty_cycle = 0.33, // duty cycle 33% + .frequency_hz = 38000, // 38KHz + .flags.polarity_active_low = false, // carrier should modulated to high level + }; + // modulate carrier to TX channel + ESP_ERROR_CHECK(rmt_apply_carrier(tx_chan, &tx_carrier_cfg)); -Transmit Mode -^^^^^^^^^^^^^ + rmt_carrier_config_t rx_carrier_cfg = { + .duty_cycle = 0.33, // duty cycle 33% + .frequency_hz = 25000, // 25KHz carrier, should be smaller than transmitter's carrier frequency + .flags.polarity_active_low = false, // the carrier is modulated to high level + }; + // demodulate carrier from RX channel + ESP_ERROR_CHECK(rmt_apply_carrier(rx_chan, &rx_carrier_cfg)); -When configuring channel in transmit mode, set **tx_config** and the following members of :cpp:type:`rmt_tx_config_t`: - -.. list:: - - * Transmit the currently configured data items in a loop - **loop_en** - * Enable the RMT carrier signal - **carrier_en** - * Frequency of the carrier in Hz - **carrier_freq_hz** - * Duty cycle of the carrier signal in percent (%) - **carrier_duty_percent** - * Level of the RMT output, when the carrier is applied - **carrier_level** - * Enable the RMT output if idle - **idle_output_en** - * Set the signal level on the RMT output if idle - **idle_level** - :SOC_RMT_SUPPORT_TX_LOOP_COUNT: * Specify maximum number of transmissions in a loop - **loop_count** - -Receive Mode -^^^^^^^^^^^^ - -In receive mode, set **rx_config** and the following members of :cpp:type:`rmt_rx_config_t`: - -.. list:: - - * Enable a filter on the input of the RMT receiver - **filter_en** - * A threshold of the filter, set in the number of ticks - **filter_ticks_thresh**. Pulses shorter than this setting will be filtered out. Note, that the range of entered tick values is [0..255]. - * A pulse length threshold that will turn the RMT receiver idle, set in number of ticks - **idle_threshold**. The receiver will ignore pulses longer than this setting. - :SOC_RMT_SUPPORT_RX_DEMODULATION: * Enable the RMT carrier demodulation - **carrier_rm** - :SOC_RMT_SUPPORT_RX_DEMODULATION: * Frequency of the carrier in Hz - **carrier_freq_hz** - :SOC_RMT_SUPPORT_RX_DEMODULATION: * Duty cycle of the carrier signal in percent (%) - **carrier_duty_percent** - :SOC_RMT_SUPPORT_RX_DEMODULATION: * Level of the RMT input, where the carrier is modulated to - **carrier_level** - -Finalize Configuration -^^^^^^^^^^^^^^^^^^^^^^ - -Once the :cpp:type:`rmt_config_t` structure is populated with parameters, it should be then invoked with :cpp:func:`rmt_config` to make the configuration effective. - -The last configuration step is installation of the driver in memory by calling :cpp:func:`rmt_driver_install`. If `rx_buf_size` parameter of this function is > 0, then a ring buffer for incoming data will be allocated. A default ISR handler will be installed, see a note in `Use Interrupts`_. - -Now, depending on how the channel is configured, we are ready to either `Transmit Data`_ or `Receive Data`_. This is described in next two sections. - - -Transmit Data -------------- - -Before being able to transmit some RMT pulses, we need to define the pulse pattern. The minimum pattern recognized by the RMT controller, later called an 'item', is provided in a structure :cpp:type:`rmt_item32_t`. Each item consists of two pairs of two values. The first value in a pair describes the signal duration in ticks and is 15 bits long, the second provides the signal level (high or low) and is contained in a single bit. A block of couple of items and the structure of an item is presented below. - -.. packetdiag:: - :caption: Structure of RMT items (L - signal level) - :align: center - - packetdiag rmt_items { - colwidth = 32 - node_width = 10 - node_height = 24 - default_fontsize = 12 - - 0-14: Period (15) - 15: L - 16-30: Period (15) - 31: L - 32-95: ... [colheight=2] - 96-110: Period (15) - 111: L - 112-126: Period (15) - 127: L - } - -For a simple example how to define a block of items see :example:`peripherals/rmt/morse_code`. - -The items are provided to the RMT controller by calling function :cpp:func:`rmt_write_items`. This function also automatically triggers start of transmission. It may be called to wait for transmission completion or exit just after transmission start. In such case you can wait for the transmission end by calling :cpp:func:`rmt_wait_tx_done`. This function does not limit the number of data items to transmit. It is using an interrupt to successively copy the new data chunks to RMT's internal memory as previously provided data are sent out. - -Another way to provide data for transmission is by calling :cpp:func:`rmt_fill_tx_items`. In this case transmission is not started automatically. To control the transmission process use :cpp:func:`rmt_tx_start` and :cpp:func:`rmt_tx_stop`. The number of items to sent is restricted by the size of memory blocks allocated in the RMT controller's internal memory, see :cpp:func:`rmt_set_mem_block_num`. - - -Receive Data ------------- - -.. only:: esp32 - - .. warning:: - RMT RX channel can't receive packet whose items are larger than its memory block size. If you set the memory block number to 1, then this RX channel can't receive packet with more than 64 items. This is a hardware limitation. - -.. only:: esp32 - - Before starting the receiver we need some storage for incoming items. The RMT controller has 512 x 32-bits of internal RAM shared between all eight channels. - -.. only:: esp32s2 - - Before starting the receiver we need some storage for incoming items. The RMT controller has 256 x 32-bits of internal RAM shared between all four channels. - -.. only:: esp32c3 - - Before starting the receiver we need some storage for incoming items. The RMT controller has 192 x 32-bits of internal RAM shared between all four channels. - -.. only:: esp32s3 - - Before starting the receiver we need some storage for incoming items. The RMT controller has 384 x 32-bits of internal RAM shared between all eight channels. - -In typical scenarios it is not enough as an ultimate storage for all incoming (and outgoing) items. Therefore this API supports retrieval of incoming items on the fly to save them in a ring buffer of a size defined by the user. The size is provided when calling :cpp:func:`rmt_driver_install` discussed above. To get a handle to this buffer call :cpp:func:`rmt_get_ringbuf_handle`. - -With the above steps complete we can start the receiver by calling :cpp:func:`rmt_rx_start` and then move to checking what's inside the buffer. To do so, you can use common FreeRTOS functions that interact with the ring buffer. Please see an example how to do it in :example:`peripherals/rmt/ir_protocols`. - -To stop the receiver, call :cpp:func:`rmt_rx_stop`. - - -Change Operation Parameters ---------------------------- - -Previously described function :cpp:func:`rmt_config` provides a convenient way to set several configuration parameters in one shot. This is usually done on application start. Then, when the application is running, the API provides an alternate way to update individual parameters by calling dedicated functions. Each function refers to the specific RMT channel provided as the first input parameter. Most of the functions have `_get_` counterpart to read back the currently configured value. - - -Parameters Common to Transmit and Receive Mode -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -* Selection of a GPIO pin number on the input or output of the RMT - :cpp:func:`rmt_set_gpio` -* Number of memory blocks allocated for the incoming or outgoing data - :cpp:func:`rmt_set_mem_pd` -* Setting of the clock divider - :cpp:func:`rmt_set_clk_div` -* Selection of the clock source, note that currently one clock source is supported, the APB clock which is 80Mhz - :cpp:func:`rmt_set_source_clk` - - -Transmit Mode Parameters +Register Event Callbacks ^^^^^^^^^^^^^^^^^^^^^^^^ -* Enable or disable the loop back mode for the transmitter - :cpp:func:`rmt_set_tx_loop_mode` -* Binary level on the output to apply the carrier - :cpp:func:`rmt_set_tx_carrier`, selected from :cpp:type:`rmt_carrier_level_t` -* Determines the binary level on the output when transmitter is idle - :cpp:func:`rmt_set_idle_level()`, selected from :cpp:type:`rmt_idle_level_t` +When an RMT channel finishes transmitting or receiving, a specific event will be generated and notify the CPU by interrupt. If you have some function that needs to be called when those events occurred, you can hook your function to the ISR (Interrupt Service Routine) by calling :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` for TX and RX channel respectively. Since the registered callback functions are called in the interrupt context, user should ensure the callback function doesn't attempt to block (e.g. by making sure that only FreeRTOS APIs with ``ISR`` suffix are called from within the function). The callback function has a boolean return value, to tell the caller whether a high priority task is woke up by it. -.. only:: SOC_RMT_SUPPORT_TX_LOOP_COUNT +TX channel supported event callbacks are listed in the :cpp:type:`rmt_tx_event_callbacks_t`: - * Enable or disable loop count feature to automatically transmit items for N iterations, then trigger an ISR callback - :cpp:func:`rmt_set_tx_loop_count` - * Enable automatically stopping when the number of iterations matches the set loop count. Note this is not reliable for target that doesn't support `SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP`. - :cpp:func:`rmt_enable_tx_loop_autostop` +- :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done` sets a callback function for trans done event. The function prototype is declared in :cpp:type:`rmt_tx_done_callback_t`. +RX channel supported event callbacks are listed in the :cpp:type:`rmt_rx_event_callbacks_t`: -Receive Mode Parameters +- :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` sets a callback function for receive complete event. The function prototype is declared in :cpp:type:`rmt_rx_done_callback_t`. + +User can save own context in :cpp:func:`rmt_tx_register_event_callbacks` and :cpp:func:`rmt_rx_register_event_callbacks` as well, via the parameter ``user_data``. The user data will be directly passed to each callback function. + +In the callback function, users can fetch the event specific data that is filled by the driver in the ``edata``. Note that the ``edata`` pointer is only valid for the duration of the callback. + +The TX done event data is defined in :cpp:type:`rmt_tx_done_event_data_t`: + +- :cpp:member:`rmt_tx_done_event_data_t::num_symbols` tells the number of transmitted RMT symbols. This also reflects the size of encoding artifacts. + +The RX complete event data is defined in :cpp:type:`rmt_rx_done_event_data_t`: + +- :cpp:member:`rmt_rx_done_event_data_t::received_symbols` points to the received RMT symbols. These symbols are saved in the ``buffer`` parameter of :cpp:func:`rmt_receive` function. User shouldn't free this receive buffer before the callback returns. +- :cpp:member:`rmt_rx_done_event_data_t::num_symbols` tells the number of received RMT symbols. This value won't be bigger than ``buffer_size`` parameter of :cpp:func:`rmt_receive` function. If the ``buffer_size`` is not sufficient to accommodate all the received RMT symbols, the driver will truncate it. + +Enable and Disable channel +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +:cpp:func:`rmt_enable` must be called in advanced before transmitting or receiving RMT symbols. For transmitters, enabling a channel will enable a specific interrupt and prepare the hardware to dispatch transactions. For RX channels, enabling a channel will enable an interrupt, but the receiver is not started during this time, as it has no idea about the characteristics of the incoming signals. The receiver will be started in :cpp:func:`rmt_receive`. + +:cpp:func:`rmt_disable` does the opposite work by disabling the interrupt and clearing pending status. The transmitter and receiver will be disabled as well. + +.. code:: c + + ESP_ERROR_CHECK(rmt_enable(tx_chan)); + ESP_ERROR_CHECK(rmt_enable(rx_chan)); + +Initiate TX Transaction ^^^^^^^^^^^^^^^^^^^^^^^ -* The filter setting - :cpp:func:`rmt_set_rx_filter` -* The receiver threshold setting - :cpp:func:`rmt_set_rx_idle_thresh` -* Whether the transmitter or receiver is entitled to access RMT's memory - :cpp:func:`rmt_set_memory_owner`, selection is from :cpp:type:`rmt_mem_owner_t`. +RMT is a special communication peripheral as it's unable to transmit raw byte streams like SPI and I2C. RMT can only send data in its own format :cpp:type:`rmt_symbol_word_t`. However, the hardware doesn't help to convert the user data into RMT symbols, this can only be done in software --- by the so-called **RMT Encoder**. The encoder is responsible for encoding user data into RMT symbols and then write to RMT memory block or DMA buffer. For how to create an RMT encoder, please refer to `RMT Encoder <#rmt-encoder>`__. +Once we got an encoder, we can initiate a TX transaction by calling :cpp:func:`rmt_transmit`. This function takes several positional parameters like channel handle, encoder handle, payload buffer. Besides that, we also need to provide a transmission specific configuration in :cpp:type:`rmt_transmit_config_t`: -Use Interrupts --------------- - -Registering of an interrupt handler for the RMT controller is done be calling :cpp:func:`rmt_isr_register`. +- :cpp:member:`rmt_transmit_config_t::loop_count` sets the number of transmission loop. After the transmitter finished one round of transmission, it can restart the same transmission again if this value is not set to zero. As the loop is controlled by hardware, the RMT channel can be used to generate many periodic sequences at the cost of a very little CPU intervention. Specially, setting :cpp:member:`rmt_transmit_config_t::loop_count` to `-1` means an infinite loop transmission. In this situation, the channel won't stop until manually call of :cpp:func:`rmt_disable`. And the trans done event won't be generated as well. If :cpp:member:`rmt_transmit_config_t::loop_count` is set to a positive number, the trans done event won't be generated until target number of loop transmission have finished. Note that, the **loop transmit** feature is not supported on all ESP chips, please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before you configure this option. Or you might encounter :c:macro:`ESP_ERR_NOT_SUPPORTED` error. +- :cpp:member:`rmt_transmit_config_t::eot_level` sets the output level when the transmitter finishes working or stops working by calling :cpp:func:`rmt_disable`. .. note:: - When calling :cpp:func:`rmt_driver_install` to use the system RMT driver, a default ISR is being installed. In such a case you cannot register a generic ISR handler with :cpp:func:`rmt_isr_register`. + There's a limitation in the transmission size if the :cpp:member:`rmt_transmit_config_t::loop_count` is set to non-zero (i.e. to enable the loop feature). The encoded RMT symbols should not exceed the capacity of RMT hardware memory block size. Or you might see error message like ``encoding artifacts can't exceed hw memory block for loop transmission``. If you have to start a large transaction by loop, you can try either: -The RMT controller triggers interrupts on four specific events describes below. To enable interrupts on these events, the following functions are provided: + - Increase the :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols`. This approach doesn't work if the DMA backend is also enabled. + - Customize an encoder and construct a forever loop in the encoding function. See also `RMT Encoder <#rmt-encoder>`__. -* The RMT receiver has finished receiving a signal - :cpp:func:`rmt_set_rx_intr_en` -* The RMT transmitter has finished transmitting the signal - :cpp:func:`rmt_set_tx_intr_en` -* The number of events the transmitter has sent matches a threshold value :cpp:func:`rmt_set_tx_thr_intr_en` -* Ownership to the RMT memory block has been violated - :cpp:func:`rmt_set_err_intr_en` +Internally, :cpp:func:`rmt_transmit` will construct a transaction descriptor and send to a job queue, which will be dispatched in the ISR. So it is possible that the transaction is not started yet when :cpp:func:`rmt_transmit` returns. To ensure all pending transaction to complete, user can use :cpp:func:`rmt_tx_wait_all_done`. -When servicing an interrupt within an ISR, the interrupt need to explicitly cleared. To do so, set specific bits described as ``RMT.int_clr.val.chN_event_name`` and defined as a ``volatile struct`` in :component_file:`soc/{IDF_TARGET_PATH_NAME}/include/soc/rmt_struct.h`, where N is the RMT channel number [0, n] and the ``event_name`` is one of four events described above. +Multiple Channels Simultaneous Transmission +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -If you do not need an ISR anymore, you can deregister it by calling a function :cpp:func:`rmt_isr_deregister`. +In some real-time control applications, we don't want any time drift in between when startup multiple TX channels. For example, to make two robotic arms move simultaneously. The RMT driver can help to manage this by creating a so-called **Sync Manager**. The sync manager is represented by :cpp:type:`rmt_sync_manager_handle_t` in the driver. The procedure of RMT sync transmission is shown as follows: -.. warning:: +.. figure:: /../_static/rmt_tx_sync.png + :align: center + :alt: RMT TX Sync - It's not recommended for users to register an interrupt handler in their applications. RMT driver is highly dependent on interrupt, especially when doing transaction in a ping-pong way, so the driver itself has registered a default handler called ``rmt_driver_isr_default``. - Instead, if what you want is to get a notification when transaction is done, go ahead with :cpp:func:`rmt_register_tx_end_callback`. + RMT TX Sync +Install RMT Sync Manager +~~~~~~~~~~~~~~~~~~~~~~~~ -Uninstall Driver ----------------- +To create a sync manager, user needs to tell which channels are going to be managed in the :cpp:type:`rmt_sync_manager_config_t`: -If the RMT driver has been installed with :cpp:func:`rmt_driver_install` for some specific period of time and then not required, the driver may be removed to free allocated resources by calling :cpp:func:`rmt_driver_uninstall`. +- :cpp:member:`rmt_sync_manager_config_t::tx_channel_array` points to the array of TX channels to be managed. +- :cpp:member:`rmt_sync_manager_config_t::array_size` sets the number of channels to be managed. +:cpp:func:`rmt_new_sync_manager` can return a manager handle on success. This function could also fail due to various errors such as invalid arguments, etc. Specially, when the sync manager has been installed before, and there're no hardware resources to create another manager, this function will report :c:macro:`ESP_ERR_NOT_FOUND` error. In addition, if the sync manager is not supported by the hardware, it will report :c:macro:`ESP_ERR_NOT_SUPPORTED` error. Please refer to [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] before using the sync manager feature. + +Start Transmission Simultaneously +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +For any managed TX channel, it won't start the machine until all the channels in the :cpp:member:`rmt_sync_manager_config_t::tx_channel_array` are called with :cpp:func:`rmt_transmit`. Before that, the channel is just put in a waiting state. Different channel usually take different time to finish the job if the transaction is different, which results in a loss of sync. So user needs to call :cpp:func:`rmt_sync_reset` to pull the channels back to the starting line again before restarting a simultaneous transmission. + +Calling :cpp:func:`rmt_del_sync_manager` can recycle the sync manager and enable the channels to initiate transactions independently afterwards. + +.. code:: c + + rmt_channel_handle_t tx_channels[2] = {NULL}; // declare two channels + int tx_gpio_number[2] = {0, 2}; + // install channels one by one + for (int i = 0; i < 2; i++) { + rmt_tx_channel_config_t tx_chan_config = { + .clk_src = RMT_CLK_SRC_DEFAULT, // select source clock + .gpio_num = tx_gpio_number[i], // GPIO number + .mem_block_symbols = 64, // memory block size, 64 * 4 = 256Bytes + .resolution_hz = 1 * 1000 * 1000, // 1MHz resolution + .trans_queue_depth = 1, // set the number of transactions that can pend in the background + }; + ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, &tx_channels[i])); + } + // install sync manager + rmt_sync_manager_handle_t synchro = NULL; + rmt_sync_manager_config_t synchro_config = { + .tx_channel_array = tx_channels, + .array_size = sizeof(tx_channels) / sizeof(tx_channels[0]), + }; + ESP_ERROR_CHECK(rmt_new_sync_manager(&synchro_config, &synchro)); + + ESP_ERROR_CHECK(rmt_transmit(tx_channels[0], led_strip_encoders[0], led_data, led_num * 3, &transmit_config)); + // tx_channels[0] won't start transmission until call of `rmt_transmit()` for tx_channels[1] returns + ESP_ERROR_CHECK(rmt_transmit(tx_channels[1], led_strip_encoders[1], led_data, led_num * 3, &transmit_config)); + +Initiate RX Transaction +^^^^^^^^^^^^^^^^^^^^^^^ + +As also discussed in the `Enable and Disable channel <#enable-and-disable-channel>`__, the RX channel still doesn't get ready to receive RMT symbols even user calls :cpp:func:`rmt_enable`. User needs to specify the basic characteristics of the incoming signals in :cpp:type:`rmt_receive_config_t`: + +- :cpp:member:`rmt_receive_config_t::signal_range_min_ns` specifies the minimal valid pulse duration (either high or low level). A pulse whose width is smaller than this value will be treated as glitch and ignored by the hardware. +- :cpp:member:`rmt_receive_config_t::signal_range_max_ns` specifies the maximum valid pulse duration (either high or low level). A pulse whose width is bigger than this value will be treated as **Stop Signal**, and the receiver will generate receive complete event immediately. + +The RMT receiver will start the RX machine after user calls :cpp:func:`rmt_receive` with the provided configuration above. Note that, this configuration is transaction specific, which means, to start a new round of reception, user needs to sets the :cpp:type:`rmt_receive_config_t` again. The receiver saves the incoming signals into its internal memory block or DMA buffer, in the format of :cpp:type:`rmt_symbol_word_t`. + +.. only:: SOC_RMT_SUPPORT_RX_PINGPONG + + Due to the limited size of memory block, the RMT receiver will notify the driver to copy away the accumulated symbols in a ping-pong way. + +.. only:: not SOC_RMT_SUPPORT_RX_PINGPONG + + Due to the limited size of memory block, the RMT receiver can only save short frames whose length is not longer than the memory block capacity. Long frames will be truncated by the hardware, and the driver will report an error message: ``hw buffer too small, received symbols truncated``. + +The copy destination should be provided in the ``buffer`` parameter of :cpp:func:`rmt_receive` function. If this buffer size is not sufficient, the receiver can continue to work but later incoming symbols will be dropped and report an error message: ``user buffer too small, received symbols truncated``. Please take care of the lifecycle of the ``buffer`` parameter, user shouldn't recycle the buffer before the receiver finished or stopped working. + +The receiver will be stopped by the driver when it finishes working (i.e. received a signal whose duration is bigger than :cpp:member:`rmt_receive_config_t::signal_range_max_ns`). User needs to call :cpp:func:`rmt_receive` again to restart the receiver, is necessary. User can get the received data in the :cpp:member:`rmt_rx_event_callbacks_t::on_recv_done` callback. See also `Register Event Callbacks <#register-event-callbacks>`__ for more information. + +.. code:: c + + static bool example_rmt_rx_done_callback(rmt_channel_handle_t channel, rmt_rx_done_event_data_t *edata, void *user_data) + { + BaseType_t high_task_wakeup = pdFALSE; + TaskHandle_t task_to_notify = (TaskHandle_t)user_data; + // send the received RMT symbols to the parser task + xTaskNotifyFromISR(task_to_notify, (uint32_t)edata, eSetValueWithOverwrite, &high_task_wakeup); + // return whether any task is woken up + return high_task_wakeup == pdTRUE; + } + + TaskHandle_t cur_task = xTaskGetCurrentTaskHandle(); + rmt_rx_event_callbacks_t cbs = { + .on_recv_done = example_rmt_rx_done_callback, + }; + ESP_ERROR_CHECK(rmt_rx_register_event_callbacks(rx_channel, &cbs, cur_task)); + + // the following timing requirement is based on NEC protocol + rmt_receive_config_t receive_config = { + .signal_range_min_ns = 1250, // the shortest duration for NEC signal is 560us, 1250ns < 560us, valid signal won't be treated as noise + .signal_range_max_ns = 12000000, // the longest duration for NEC signal is 9000us, 12000000ns > 9000us, the receive won't stop early + }; + + rmt_symbol_word_t raw_symbols[64]; // 64 symbols should be sufficient for a standard NEC frame + // ready to receive + ESP_ERROR_CHECK(rmt_receive(rx_channel, raw_symbols, sizeof(raw_symbols), &receive_config)); + // wait for RX done signal + rmt_rx_done_event_data_t *rx_data = NULL; + xTaskNotifyWait(0x00, ULONG_MAX, (uint32_t *)&rx_data, portMAX_DELAY); + // parse the receive symbols + example_parse_nec_frame(rx_data->received_symbols, rx_data->num_symbols); + +RMT Encoder +^^^^^^^^^^^ + +An RMT encoder is part of the RMT TX transaction, whose responsibility is to generate and write the correct RMT symbols into hardware memory (or DMA buffer) at specific time. There're some special restrictions for an encoding function: + +- An encoding function might be called for several times within a single transaction. This is because the target RMT memory block can't accommodate all the artifacts at once. We have to use the memory in a **ping-pong** way, thus the encoding session is divided into multiple parts. This requires the encoder to be **stateful**. +- The encoding function is running in the ISR context. To speed up the encoding session, it's high recommend to put the encoding function into IRAM. This can also avoid the cache miss during encoding. + +To help get started with RMT driver faster, some commonly used encoders are provided out-of-the box. They can either work alone or chained together into a new encoder. See also `Composite Pattern `__ for the principle behind. The driver has defined the encoder interface in :cpp:type:`rmt_encoder_t`, it contains the following functions: + +- :cpp:member:`rmt_encoder_t::encode` is the fundamental function of an encoder. This is where the encoding session happens. Please note, the :cpp:member:`rmt_encoder_t::encode` function might be called for multiple times within a single transaction. The encode function should return the state of current encoding session. The supported states are listed in the :cpp:type:`rmt_encode_state_t`. If the result contains :cpp:enumerator:`RMT_ENCODING_COMPLETE`, it means the current encoder has finished work. If the result contains :cpp:enumerator:`RMT_ENCODING_MEM_FULL`, we need to yield from current session, as there's no space to save more encoding artifacts. +- :cpp:member:`rmt_encoder_t::reset` should reset the encoder state back to initial. The RMT encoder is stateful, if RMT transmitter stopped manually without its corresponding encoder being reset, then the following encoding session can be wrong. This function is also called implicitly in :cpp:func:`rmt_disable`. +- :cpp:member:`rmt_encoder_t::del` function should free the resources allocated by the encoder. + +Copy Encoder +~~~~~~~~~~~~ + +A copy encoder is created by calling :cpp:func:`rmt_new_copy_encoder`. Copy encoder's main functionality is to copy the RMT symbols from user space into the driver layer. It's usually used to encode const data (i.e. data won't change at runtime after initialization), for example, the leading code in the IR protocol. + +A configuration structure :cpp:type:`rmt_copy_encoder_config_t` should be provided in advance before calling :cpp:func:`rmt_new_copy_encoder`. Currently, this configuration is reserved for future expansion. + +Bytes Encoder +~~~~~~~~~~~~~ + +A bytes encoder is created by calling :cpp:func:`rmt_new_bytes_encoder`. Bytes encoder's main functionality is to convert the user space byte stream into RMT symbols dynamically. It's usually used to encode dynamic data, for example, the address and command fields in the IR protocol. + +A configuration structure :cpp:type:`rmt_bytes_encoder_config_t` should be provided in advance before calling :cpp:func:`rmt_new_bytes_encoder`: + +- :cpp:member:`rmt_bytes_encoder_config_t::bit0` and :cpp:member:`rmt_bytes_encoder_config_t::bit1` are necessary to tell to the encoder how to represent bit zero and bit one in the format of :cpp:type:`rmt_symbol_word_t`. +- :cpp:member:`rmt_bytes_encoder_config_t::msb_first` sets the encoding order for of byte. If it is set to true, the encoder will encode the **Most Significant Bit** first. Otherwise, it will encode the **Least Significant Bit** first. + +Besides the primitive encoders provided by the driver, user can implement his own encoder by chaining the existing encoders together. A common encoder chain is shown as follows: + +.. blockdiag:: /../_static/diagrams/rmt/rmt_encoder_chain.diag + :caption: RMT Encoder Chain + :align: center + +Customize RMT Encoder for NEC Protocol +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In this section, we will demonstrate on how to write an NEC encoder. The NEC IR protocol uses pulse distance encoding of the message bits. Each pulse burst is `562.5µs` in length, logical bits are transmitted as follows. It is worth mentioning, the bytes of data bits are sent least significant bit first. + +- Logical ``0``: a `562.5µs` pulse burst followed by a `562.5µs` space, with a total transmit time of `1.125ms` +- Logical ``1``: a `562.5µs` pulse burst followed by a `1.6875ms` space, with a total transmit time of `2.25ms` + +When a key is pressed on the remote controller, the message transmitted consists of the following, in order: + +.. figure:: /../_static/ir_nec.png + :align: center + :alt: IR NEC Frame + + IR NEC Frame + +- `9ms` leading pulse burst (also called the "AGC pulse") +- `4.5ms` space +- 8-bit address for the receiving device +- 8-bit logical inverse of the address +- 8-bit command +- 8-bit logical inverse of the command +- a final `562.5µs` pulse burst to signify the end of message transmission + +Then we can construct the NEC :cpp:member:`rmt_encoder_t::encode` function in the same order, for example: + +.. code:: c + + // IR NEC scan code representation + typedef struct { + uint16_t address; + uint16_t command; + } ir_nec_scan_code_t; + + // construct a encoder by combining primitive encoders + typedef struct { + rmt_encoder_t base; // the base "class", declares the standard encoder interface + rmt_encoder_t *copy_encoder; // use the copy_encoder to encode the leading and ending pulse + rmt_encoder_t *bytes_encoder; // use the bytes_encoder to encode the address and command data + rmt_symbol_word_t nec_leading_symbol; // NEC leading code with RMT representation + rmt_symbol_word_t nec_ending_symbol; // NEC ending code with RMT representation + int state; // record the current encoding state (i.e. we're in which encoding phase) + } rmt_ir_nec_encoder_t; + + static size_t rmt_encode_ir_nec(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) + { + rmt_ir_nec_encoder_t *nec_encoder = __containerof(encoder, rmt_ir_nec_encoder_t, base); + rmt_encode_state_t session_state = 0; + rmt_encode_state_t state = 0; + size_t encoded_symbols = 0; + ir_nec_scan_code_t *scan_code = (ir_nec_scan_code_t *)primary_data; + rmt_encoder_handle_t copy_encoder = nec_encoder->copy_encoder; + rmt_encoder_handle_t bytes_encoder = nec_encoder->bytes_encoder; + switch (nec_encoder->state) { + case 0: // send leading code + encoded_symbols += copy_encoder->encode(copy_encoder, channel, &nec_encoder->nec_leading_symbol, + sizeof(rmt_symbol_word_t), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 1; // we can only switch to next state when current encoder finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + // fall-through + case 1: // send address + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->address, sizeof(uint16_t), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 2; // we can only switch to next state when current encoder finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + // fall-through + case 2: // send command + encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, &scan_code->command, sizeof(uint16_t), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 3; // we can only switch to next state when current encoder finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + // fall-through + case 3: // send ending code + encoded_symbols += copy_encoder->encode(copy_encoder, channel, &nec_encoder->nec_ending_symbol, + sizeof(rmt_symbol_word_t), &session_state); + if (session_state & RMT_ENCODING_COMPLETE) { + nec_encoder->state = 0; // back to the initial encoding session + state |= RMT_ENCODING_COMPLETE; // telling the caller the NEC encoding has finished + } + if (session_state & RMT_ENCODING_MEM_FULL) { + state |= RMT_ENCODING_MEM_FULL; + goto out; // yield if there's no free space to put other encoding artifacts + } + } + out: + *ret_state = state; + return encoded_symbols; + } + +A full sample code can be found in :example:`peripherals/rmt/ir_nec_transceiver`. In the above snippet, we use a ``switch-case`` plus several ``goto`` statements to implement a `state machine `__ . With this pattern, user can construct a lot more complex IR protocols. + +Power Management +^^^^^^^^^^^^^^^^ + +When power management is enabled (i.e. :ref:`CONFIG_PM_ENABLE` is on), the system will adjust the APB frequency before going into light sleep, thus potentially changing the resolution of RMT internal counter. + +However, the driver can prevent the system from changing APB frequency by acquiring a power management lock of type :cpp:enumerator:`ESP_PM_APB_FREQ_MAX`. Whenever user creates an RMT channel that has selected :cpp:enumerator:`RMT_CLK_SRC_APB` as the clock source, the driver will guarantee that the power management lock is acquired after the channel enabled by :cpp:func:`rmt_enable`. Likewise, the driver releases the lock after :cpp:func:`rmt_disable` is called for the same channel. This also reveals that the :cpp:func:`rmt_enable` and :cpp:func:`rmt_disable` should appear in pairs. + +If the channel clock source is selected to others like :cpp:enumerator:`RMT_CLK_SRC_XTAL`, then the driver won't install power management lock for it, which is more suitable for a low power application as long as the source clock can still provide sufficient resolution. + +IRAM Safe +^^^^^^^^^ + +By default, the RMT interrupt will be deferred when the Cache is disabled for reasons like writing/erasing the main Flash. Thus the transaction done interrupt will not get executed in time, which is not expected in a real-time application. What's worse, when the RMT transaction relies on **ping-pong** interrupt to successively encode or copy RMT symbols, such delayed response can lead to an unpredictable result. + +There's a Kconfig option :ref:`CONFIG_RMT_ISR_IRAM_SAFE` that will: + +1. Enable the interrupt being serviced even when cache is disabled +2. Place all functions that used by the ISR into IRAM [2]_ +3. Place driver object into DRAM (in case it's mapped to PSRAM by accident) + +This Kconfig option will allow the interrupt to run while the cache is disabled but will come at the cost of increased IRAM consumption. + +Thread Safety +^^^^^^^^^^^^^ + +The factory function :cpp:func:`rmt_new_tx_channel`, :cpp:func:`rmt_new_rx_channel` and :cpp:func:`rmt_new_sync_manager` are guaranteed to be thread safe by the driver, which means, user can call them from different RTOS tasks without protection by extra locks. +Other functions that take the :cpp:type:`rmt_channel_handle_t` and :cpp:type:`rmt_sync_manager_handle_t` as the first positional parameter, are not thread safe. which means the user should avoid calling them from multiple tasks. + +Kconfig Options +^^^^^^^^^^^^^^^ + +- :ref:`CONFIG_RMT_ISR_IRAM_SAFE` controls whether the default ISR handler can work when cache is disabled, see also `IRAM Safe <#iram-safe>`__ for more information. +- :ref:`CONFIG_RMT_ENABLE_DEBUG_LOG` is used to enabled the debug log at the cost of increased firmware binary size. Application Examples -------------------- -* Using RMT to send morse code: :example:`peripherals/rmt/morse_code`. -* Using RMT to drive RGB LED strip: :example:`peripherals/rmt/led_strip`. -* NEC remote control TX and RX example: :example:`peripherals/rmt/ir_protocols`. -* Musical buzzer example: :example:`peripherals/rmt/musical_buzzer`. - +* RMT based RGB LED strip customized encoder: :example:`peripherals/rmt/led_strip` +* RMT IR NEC protocol encoding and decoding: :example:`peripherals/rmt/ir_nec_transceiver` +* RMT transactions in queue: :example:`peripherals/rmt/musical_buzzer` +* RMT based stepper motor with S-curve algorithm: : :example:`peripherals/rmt/stepper_motor` +* RMT infinite loop for driving DShot ESC: :example:`peripherals/rmt/dshot_esc` API Reference ------------- -.. include-build-file:: inc/rmt.inc -.. include-build-file:: inc/rmt_types.inc +.. include-build-file:: inc/rmt_tx.inc +.. include-build-file:: inc/rmt_rx.inc +.. include-build-file:: inc/rmt_common.inc +.. include-build-file:: inc/rmt_encoder.inc +.. include-build-file:: inc/components/driver/include/driver/rmt_types.inc +.. include-build-file:: inc/components/hal/include/hal/rmt_types.inc + +.. [1] + Different ESP chip series might have different number of RMT channels. Please refer to the [`TRM <{IDF_TARGET_TRM_EN_URL}#rmt>`__] for details. The driver won't forbid you from applying for more RMT channels, but it will return error when there's no hardware resources available. Please always check the return value when doing `Resource Allocation <#resource-allocation>`__. + +.. [2] + Callback function (e.g. :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done`) and the functions invoked by itself should also reside in IRAM, users need to take care of this by themselves. diff --git a/docs/en/migration-guides/peripherals.rst b/docs/en/migration-guides/peripherals.rst index 14aa10c140..3280198a3e 100644 --- a/docs/en/migration-guides/peripherals.rst +++ b/docs/en/migration-guides/peripherals.rst @@ -128,9 +128,58 @@ I2C RMT Driver ---------- + RMT driver has been redesigned (see :doc:`RMT transceiver <../api-reference/peripherals/rmt>`), which aims to unify and extend the usage of RMT peripheral. Although it's recommended to use the new driver APIs, the legacy driver is still available in the previous include path ``driver/rmt.h``. However, by default, including ``driver/rmt.h`` will bring a build warning like `The legacy RMT driver is deprecated, please use driver/rmt_tx.h and/or driver/rmt_rx.h`. The warning can be suppressed by the Kconfig option :ref:`CONFIG_RMT_SUPPRESS_DEPRECATE_WARN`. + + The major breaking changes in concept and usage are listed as follows: + + Breaking Changes in Concepts + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + - ``rmt_channel_t`` which used to identify the hardware channel are removed from user space. In the new driver, RMT channel is represented by :cpp:type:`rmt_channel_handle_t`. The channel is dynamic allocated by the driver, instead of designated by user. + - ``rmt_item32_t`` is replaced by :cpp:type:`rmt_symbol_word_t`, which avoids a nested union inside a struct. + - ``rmt_mem_t`` is removed, as we don't allow users to access RMT memory block (a.k.an RMTMEM) directly. Direct access to RMTMEM doesn't make sense but make mistakes, especially when the RMT channel also connected with a DMA channel. + - ``rmt_mem_owner_t`` is removed, as the ownership is controller by driver, not by user anymore. + - ``rmt_source_clk_t`` is replaced by :cpp:type:`rmt_clock_source_t`, note they're not binary compatible. + - ``rmt_data_mode_t`` is removed, the RMT memory access mode is configured to always use Non-FIFO and DMA mode. + - ``rmt_mode_t`` is removed, as the driver has stand alone install functions for TX and RX channels. + - ``rmt_idle_level_t`` is removed, setting IDLE level for TX channel is available in :cpp:member:`rmt_transmit_config_t::eot_level`. + - ``rmt_carrier_level_t`` is removed, setting carrier polarity is available in :cpp:member:`rmt_carrier_config_t::polarity_active_low`. + - ``rmt_channel_status_t`` and ``rmt_channel_status_result_t`` are removed, they're not used anywhere. + - transmitting by RMT channel doesn't expect user to prepare the RMT symbols, instead, user needs to provide an RMT Encoder to tell the driver how to convert user data into RMT symbols. + + + Breaking Changes in Usage + ~~~~~~~~~~~~~~~~~~~~~~~~~ + - Channel installation has been separated for TX and RX channels into :cpp:func:`rmt_new_tx_channel` and :cpp:func:`rmt_new_rx_channel`. + - ``rmt_set_clk_div`` and ``rmt_get_clk_div`` are removed. Channel clock configuration can only be done during channel installation. + - ``rmt_set_rx_idle_thresh`` and ``rmt_get_rx_idle_thresh`` are removed. In the new driver, the RX channel IDLE threshold is redesigned into a new concept :cpp:member:`rmt_receive_config_t::signal_range_max_ns`. + - ``rmt_set_mem_block_num`` and ``rmt_get_mem_block_num`` are removed. In the new driver, the memory block number is determined by :cpp:member:`rmt_tx_channel_config_t::mem_block_symbols` and :cpp:member:`rmt_rx_channel_config_t::mem_block_symbols`. + - ``rmt_set_tx_carrier`` is removed, the new driver uses :cpp:func:`rmt_apply_carrier` to set carrier behavior. + - ``rmt_set_mem_pd`` and ``rmt_get_mem_pd`` are removed. The memory power is managed by the driver automatically. + - ``rmt_memory_rw_rst``, ``rmt_tx_memory_reset`` and ``rmt_rx_memory_reset`` are removed. Memory reset is managed by the driver automatically. + - ``rmt_tx_start`` and ``rmt_rx_start`` are merged into a single function :cpp:func:`rmt_enable`, for both TX and RX channels. + - ``rmt_tx_stop`` and ``rmt_rx_stop`` are merged into a single function :cpp:func:`rmt_disable`, for both TX and RX channels. + - ``rmt_set_memory_owner`` and ``rmt_get_memory_owner`` are removed. RMT memory owner guard is added automatically by the driver. + - ``rmt_set_tx_loop_mode`` and ``rmt_get_tx_loop_mode`` are removed. In the new driver, the loop mode is configured in :cpp:member:`rmt_transmit_config_t::loop_count`. + - ``rmt_set_source_clk`` and ``rmt_get_source_clk`` are removed. Configuring clock source is only possible during channel installation by :cpp:member:`rmt_tx_channel_config_t::clk_src` and :cpp:member:`rmt_rx_channel_config_t::clk_src`. + - ``rmt_set_rx_filter`` is removed. In the new driver, the filter threshold is redesigned into a new concept :cpp:member:`rmt_receive_config_t::signal_range_min_ns`. + - ``rmt_set_idle_level`` and ``rmt_get_idle_level`` are removed. Setting IDLE level for TX channel is available in :cpp:member:`rmt_transmit_config_t::eot_level`. + - ``rmt_set_rx_intr_en``, ``rmt_set_err_intr_en``, ``rmt_set_tx_intr_en``, ``rmt_set_tx_thr_intr_en`` and ``rmt_set_rx_thr_intr_en`` are removed. The new driver doesn't allow user to turn on/off interrupt from user space. Instead, it provides callback functions. + - ``rmt_set_gpio`` and ``rmt_set_pin`` are removed. The new driver doesn't support to switch GPIO dynamically at runtime. + - ``rmt_config`` is removed. In the new driver, basic configuration is done during the channel installation stage. + - ``rmt_isr_register`` and ``rmt_isr_deregister`` are removed, the interrupt is allocated by the driver itself. + - ``rmt_driver_install`` is replaced by :cpp:func:`rmt_new_tx_channel` and :cpp:func:`rmt_new_rx_channel`. + - ``rmt_driver_uninstall`` is replaced by :cpp:func:`rmt_del_channel`. + - ``rmt_fill_tx_items``, ``rmt_write_items`` and ``rmt_write_sample`` are removed. In the new driver, user needs to provide an encoder to "translate" the user data into RMT symbols. + - ``rmt_get_counter_clock`` is removed, as the channel clock resolution is configured by user from :cpp:member:`rmt_tx_channel_config_t::resolution_hz`. + - ``rmt_wait_tx_done`` is replaced by :cpp:func:`rmt_tx_wait_all_done`. + - ``rmt_translator_init``, ``rmt_translator_set_context`` and ``rmt_translator_get_context`` are removed. In the new driver, the translator has been replaced by the RMT encoder. + - ``rmt_get_ringbuf_handle`` is removed. The new driver doesn't use Ringbuffer to save RMT symbols. Instead, the incoming data are saved to the user provided buffer directly. The user buffer can even be mounted to DMA link internally. + - ``rmt_register_tx_end_callback`` is replaced by :cpp:func:`rmt_tx_register_event_callbacks`, where user can register :cpp:member:`rmt_tx_event_callbacks_t::on_trans_done` event callback. - ``rmt_set_intr_enable_mask`` and ``rmt_clr_intr_enable_mask`` are removed, as the interrupt is handled by the driver, user doesn't need to take care of it. - - ``rmt_set_pin`` is removed, as ``rmt_set_gpio`` can do the same thing. - - ``rmt_memory_rw_rst`` is removed, user can use ``rmt_tx_memory_reset`` and ``rmt_rx_memory_reset`` for TX and RX channel respectively. + - ``rmt_add_channel_to_group`` and ``rmt_remove_channel_from_group`` are replaced by RMT sync manager. Please refer to :cpp:func:`rmt_new_sync_manager`. + - ``rmt_set_tx_loop_count`` is removed. The loop count in the new driver is configured in :cpp:member:`rmt_transmit_config_t::loop_count`. + - ``rmt_enable_tx_loop_autostop`` is removed. In the new driver, TX loop auto stop is always enabled if available, it's not configurable anymore. LCD --- diff --git a/docs/sphinx-known-warnings.txt b/docs/sphinx-known-warnings.txt index 901dc0d382..34345f1a78 100644 --- a/docs/sphinx-known-warnings.txt +++ b/docs/sphinx-known-warnings.txt @@ -70,6 +70,8 @@ If type alias or template alias: void() esp_spp_cb_t (esp_spp_cb_event_t event, esp_spp_cb_param_t *param) ----^ +rmt_encoder.inc:line: WARNING: Duplicate C++ declaration, also defined at api-reference/peripherals/rmt:line. +Declaration is '.. cpp:type:: struct rmt_encoder_t rmt_encoder_t'. spi_master.inc:line: WARNING: Duplicate C++ declaration, also defined at api-reference/peripherals/spi_master:line. Declaration is '.. cpp:type:: struct spi_transaction_t spi_transaction_t'. spi_slave.inc:line: WARNING: Duplicate C++ declaration, also defined at api-reference/peripherals/spi_slave:line.