espressif / esp-idf

Espressif IoT Development Framework. Official development framework for Espressif SoCs.
Apache License 2.0
13.88k stars 7.32k forks source link

RMT Group Clock too fast (IDFGH-13921) #14760

Open dpnebert opened 1 month ago

dpnebert commented 1 month ago

Answers checklist.

General issue report

I have an ESP32-S3-WROOM-1 and I am using ESP-IDF:

I (27) boot: ESP-IDF v5.3-dirty 2nd stage bootloader I (27) boot: compile time Oct 21 2024 10:56:10 I (27) boot: Multicore bootloader I (30) boot: chip revision: v0.2 I (34) boot.esp32s3: Boot SPI Speed : 80MHz I (39) boot.esp32s3: SPI Mode : DIO I (44) boot.esp32s3: SPI Flash Size : 8MB ... I (199) cpu_start: Pro cpu start user code I (203) cpu_start: cpu freq: 160000000 Hz I (214) app_init: Application information: I (219) app_init: Project name: rf_poc I (224) app_init: App version: 1 I (228) app_init: Compile time: Oct 21 2024 10:56:03 I (234) app_init: ELF file SHA256: 171965faf... I (240) app_init: ESP-IDF: v5.3-dirty I (251) efuse_init: Min chip rev: v0.0 I (255) efuse_init: Max chip rev: v0.99 I (260) efuse_init: Chip rev: v0.2

The signals I want to receive will be at minimum 420uS (420000nS) and maximum 1.4mS (1400000nS). The only RMT clocks available are 80MHz APB clock, ~17.5MHz RC Fast, or external XTAL.

When I do the calculations with the 80MHz APB clock, my filter_reg_value is waaaay over the allowed 8-bit value. And when I used the 17.5MHz RC Fast clock, it brought the filter_reg_value down closer to an 8-bit number.

So, I'm going to plug in 255 for my filter_reg_value and solve for the 'filter_clock_resolution_hz', which is 607142.8Hz (607.1428kHz). This tell me that if can use a 500kHz clock, my filter_reg_value should be 210, and within the 8-bit range.

Am I missing something? Can I use a 500kHz as my XTAL? Can I use LEDC to produce the clock on a GPIO connected to the XTAL inputs?

Even a clock of 1MHz would give me the filter_reg_value of 255uS. Seems the RMT with the available clocks can't do what I need?

dpnebert commented 1 month ago

As far as I can tell, if the filter_reg_value was stored in a 16-bit register, or if I had access to a slower group clock, I could do what I am attempting. Doesn't seem like 420uS and 1.4mS are unrealistic values and the RMT should be able to support it.

atanisoft commented 1 month ago

You should not have issues or need to manually poke into register values. If you use the newer RMT APIs you can have 1uSec resolution with the following TX config:

const rmt_tx_channel_config_t tx_config =
    {
        .gpio_num = config->signal_pin,
        .clk_src = RMT_CLK_SRC_DEFAULT,
        .resolution_hz = 1000000u,
        .mem_block_symbols =
            SOC_RMT_TX_CANDIDATES_PER_GROUP * SOC_RMT_MEM_WORDS_PER_CHANNEL,
        .trans_queue_depth = 2,
        .intr_priority = 3,
        .flags =
        {
            .invert_out = 0,
            .with_dma = 0,
            .io_loop_back = 0,
            .io_od_mode = 0,
        },
    };

RX should be similar.

dpnebert commented 1 month ago

@atanisoft Thank you for the suggestion. I made the switch from Arduino v2 to ESP-IDF v5 before Arduino v3 was to be released. Figured with API breaking changes, it was the best time to make the move. Here is the API page I've been referencing: https://docs.espressif.com/projects/esp-idf/en/v5.3.1/esp32s3/api-reference/peripherals/rmt.html

I believe I have been using the correct values in the calculation, but if it isn't working, must not be correct.

I am using the clk_src, 'RMT_CLK_SRC_APB' (80MHz) and my minimum signal pulse is 420,000nS.

Using the clk_src and signal_range_min_ns, we can calculate the filter_reg_value. Here is the underlying code on line 393 of https://github.com/espressif/esp-idf/blob/v5.3.1/components/esp_driver_rmt/src/rmt_rx.c

uint32_t filter_reg_value = ((uint64_t)rx_chan->filter_clock_resolution_hz * config->signal_range_min_ns) / 1000000000UL;

So, plugging in the values:

uint32_t filter_reg_value = 80000000 * 420000 / 1000000000UL;
uint32_t filter_reg_value = 33600000000000 / 1000000000UL;
uint32_t filter_reg_value = 33600;

Which seems legit, since 33,600 will fit in a uint32_t. But looking a bit further at line 395:

ESP_RETURN_ON_FALSE_ISR(filter_reg_value <= RMT_LL_MAX_FILTER_VALUE, ESP_ERR_INVALID_ARG, TAG, "signal_range_min_ns too big");

So, filter_reg_value needs to be less than or equal to RMT_LL_MAX_FILTER_VALUE, which is defined as

#define RMT_LL_MAX_FILTER_VALUE           255

I've spent hours digging and doing trial and error. I'm just waiting for an knowledgeable admin to tell me it's not possible. Almost all of our product use this timing protocol, so it'll be a huge hit.

Thank you in advance!

atanisoft commented 1 month ago

You seem to be mixing the low level and higher level APIs and going in circles. You can use strictly the higher level APIs as seen in this example to handle RX.

For filtering you would use something like this except as:

rmt_receive_config_t receive_config = {
        .signal_range_min_ns = 420000,
        .signal_range_max_ns = 1400000,
 };

This will filter out any pulses that are outside the 420-1400uSec range automatically. You could then process the received samples as seen in the example. You can ignore the TX / encoder pieces from the example since you are focusing on RX only.

dpnebert commented 1 month ago

I copied the IR NEC transceiver over to my workspace, set-target to esp32s3, then menuconfig to set my flash size, then compiled and flashed. Here is the original unchanged rmt_receive_config_t declaration and initialization:

    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
    };

And here is the snippet from the terminal:

I (316) main_task: Calling app_main()
I (316) example: create RMT RX channel
I (316) rmt: group->resolution_hz: 80000000
I (316) rmt: config->resolution_hz: 1000000
I (326) rmt: real_div: 80
I (326) rmt: rx_channel->base.resolution_hz: 1000000
I (336) gpio: GPIO[19]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (346) example: register RX done callback
I (346) example: create RMT TX channel
I (356) gpio: GPIO[18]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (356) example: modulate carrier to TX channel
I (366) example: install IR NEC encoder
I (366) example: enable RMT TX and RX channels
I (376) rmt: rx_chan->filter_clock_resolution_hz: 80000000
I (386) rmt: config->signal_range_min_ns: 1250
I (386) rmt: filter_reg_value: 100

Then I changed

.signal_range_min_ns = 1250

to

.signal_range_min_ns = 420000

And here is the terminal:

I (321) main_task: Calling app_main()
I (321) example: create RMT RX channel
I (321) rmt: group->resolution_hz: 80000000
I (321) rmt: config->resolution_hz: 1000000
I (331) rmt: real_div: 80
I (331) rmt: rx_channel->base.resolution_hz: 1000000
I (341) gpio: GPIO[19]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (351) example: register RX done callback
I (351) example: create RMT TX channel
I (361) gpio: GPIO[18]| InputEn: 0| OutputEn: 1| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0
I (361) example: modulate carrier to TX channel
I (371) example: install IR NEC encoder
I (371) example: enable RMT TX and RX channels
I (381) rmt: rx_chan->filter_clock_resolution_hz: 80000000
I (391) rmt: config->signal_range_min_ns: 420000
I (391) rmt: filter_reg_value: 33600
E (401) rmt: rmt_receive(419): signal_range_min_ns too big

I didn't know about the 8-bit limitation until I saw GitHub issue #11262

That information really should be in the API reference.

atanisoft commented 1 month ago

In that case you might just set the minimum to zero and handle the filtering in your RX parser (the NEC example validates the values are in a certain range).

dpnebert commented 1 month ago

@atanisoft Very good idea! Thanks! You actually reminded my of something I thought of on my drive home yesterday. I'll only see glitches when nothing is transmitted. But during the actual transmission, they are pretty stable.

And, for anyone following along, by setting it to zero, means division by 256, as per:

static inline void rmt_ll_rx_set_channel_clock_div(rmt_dev_t *dev, uint32_t channel, uint32_t div)
{
    HAL_ASSERT(div >= 1 && div <= 256 && "divider out of range");
    // limit the maximum divider to 256
    if (div >= 256) {
        div = 0; // 0 means 256 division
    }
    HAL_FORCE_MODIFY_U32_REG_FIELD(dev->conf_ch[channel].conf0, div_cnt, div);
}

I'll keep this open and update it when I figure it out or quit.

dpnebert commented 1 month ago

I've decided to throw in the towel. The ESP32 is amazing until it doesn't work. And I just keep find more and more deficiencies. I'm trying to make the push from programming PIC chips in assembly when the senior engineer retires. But I can't sell this if I am constantly doing "work arounds" to get done what it should be easy and quick, within a acceptable timeframe. And I don't believe you, @atanisoft , work for Espressif, but thank you for helping me. I never know if the issues I post are written horribly or if the question is so good, no one knows, not even Espressif.

@Kainarx Last shot since I see it is assigned to you. Any suggestions?

suda-morris commented 1 month ago

Hi @dpnebert Thanks for reporting the issue. The root cause is that the driver didn't provide a way to allow the user to set a clock divider for the group's clock. See:

https://github.com/espressif/esp-idf/blob/master/components/esp_driver_rmt/src/rmt_common.c#L215

Here is a quick fix to set the group clock resolution to 500KHz

--- a/components/esp_driver_rmt/src/rmt_common.c
+++ b/components/esp_driver_rmt/src/rmt_common.c
@@ -209,12 +209,12 @@ esp_err_t rmt_select_periph_clock(rmt_channel_handle_t chan, rmt_clock_source_t
 #endif // CONFIG_PM_ENABLE

     esp_clk_tree_enable_src((soc_module_clk_t)clk_src, true);
-    // no division for group clock source, to achieve highest resolution
+    // Divide group clock source by 80 to achieve a smaller clock resolution
     RMT_CLOCK_SRC_ATOMIC() {
-        rmt_ll_set_group_clock_src(group->hal.regs, channel_id, clk_src, 1, 1, 0);
+        rmt_ll_set_group_clock_src(group->hal.regs, channel_id, clk_src, 80, 1, 0);
         rmt_ll_enable_group_clock(group->hal.regs, true);
     }
-    group->resolution_hz = periph_src_clk_hz;
+    group->resolution_hz = periph_src_clk_hz / 80;
     ESP_LOGD(TAG, "group clock resolution:%"PRIu32, group->resolution_hz);
     return ret;
 }

Then when you select the XTAL as the clock source, the filter_clock_resolution_hz should be 500000.

The channel resolution can be set to 100KHz (can't be bigger than 500KHz):

    rmt_rx_channel_config_t rx_channel_cfg = {
        .clk_src = RMT_CLK_SRC_XTAL,
        .resolution_hz = 100000, // 100KHz
        .mem_block_symbols = SOC_RMT_MEM_WORDS_PER_CHANNEL,
        .gpio_num = TEST_RMT_GPIO_NUM_A,
    };

Then the receive signal range should be achieved:

    rmt_receive_config_t rx_config = {
        .signal_range_min_ns = 420000, // 420us
        .signal_range_max_ns = 1400000, // 1.4ms
    };
dpnebert commented 1 month ago

@suda-morris Thank you! I know there are a lot of peripheral that depend on those clocks, so I didn't know where I could make changes that didn't cause something to be broken down the road. And being able to (easily) modify the source is a big reason why I made the push to move away from Arduino.

And again, I've gotten pulled from this to do something unrelated. I was able to make the changes and saw the changes reflected in the TX channel. I've tied my RX to ground and it is still telling me it is receiving bits, but that is a problem for another time. As of right now, @suda-morris has helped me achieve a lower group clock. Thank you! Also, thank you @atanisoft for helping me and raising visibility on this issue.

Thank you, again!