espressif / esp-idf

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

v4.4 documentation should state that custom RMT ISR handlers are NOT supported (ESP32-S3) (IDFGH-10212) #11478

Open zyxtia opened 1 year ago

zyxtia commented 1 year ago

Answers checklist.

IDF version.

v4.4.4 (through Arduino IDE)

Operating System used.

Linux

How did you build your project?

Other (please specify in More Information)

If you are using Windows, please specify command line type.

None

Development Kit.

M5Atom-S3 Lite, (also same on StampS3)

Power Supply used.

USB

What is the expected behavior?

The RMT was changed from the ESP32 to the ESP32-S3, where channels 0-3 are TX only, and 4-7 are RX only. Choosing an RX only channel, mapping it to a GPIO, and configuring it (in much the same way as would be done on ESP32), would then end with calling "rmt_rx_start" to get the channel listening. The ESP_ERROR_CHECK passes when choosing a valid RX channel (4-7), so there shouldn't be a reason for rmt_rx_start() to fail (that I can think of). If there is something wrong with the channel config, then ESP_ERROR_CHECK should flag it, and that should be explained in the API reference. (FYI: program is intended to measure the period of a simple RC car/plane 20Hz servo motor. Those motors generally have a period of 1ms to 2ms)

What is the actual behavior?

While configuring RMT (see below, "rmt_init()" function), when calling "_rmt_rx_start(rmtchannel.channel, true)", error is thrown and system reboots. Note that the backtrace calls out file "rmt_ll.h", line 132. The contents of line 132 (for me) are: static inline uint32_t rmt_ll_rx_get_mem_blocks(rmt_dev_t *dev, uint32_t channel) { return dev->chmconf[channel].conf0.mem_size_m; }

Steps to reproduce.

  1. Arduino IDE v1.8.19 with ESP32 Core v2.0.9 (ESP-IDF 4.4.4 with v5.0 backported fixes)
  2. Choose ESP32-S3 board
  3. Flash provided code onto an ESP32-S3 board: ...
 // Code:
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/rmt.h"

#define rmt_input_GPIO 7 //on my M5Atom-S3 Lite board, this is an available GPIO
volatile uint16_t PWM_period=0; //the value i'm looking for, in microseconds
const uint8_t RMT_Channel_Number=4;
const uint8_t RMT_Connected_GPIO = rmt_input_GPIO;

void setup() {
  Serial.begin(115200);
  rmt_init(); //initialize the PWM monitor
}
void loop() {
  Serial.print(PWM_period/8);
  Serial.println("us");
}

// receiver pulse length ranges from 8000 to 16000 centered around 12000 (units: ticks)
#define RECEIVER_CH_MIN 8000  //converted to microseconds = 1000us (1ms)
#define RECEIVER_CH_CENTER 12000  //converted to microseconds = 1500us (1.5ms)
#define RECEIVER_CH_MAX 16000  //converted to microseconds = 2000us (2ms)

//note that the actual duty cycle is between 1000 and 2000 microseconds, so we're dividing by 8 to get the value we want
#define RMT_TICK_PER_US 8  //assuming 80MHz APB clock
#define RMT_RX_CLK_DIV (80000000/RMT_TICK_PER_US/1000000) //80,000,000/8/1,000,000 = 10. 
#define RMT_RX_MAX_US 3500   // time before receiver goes idle, milliseconds

static void rmt_isr_handler(void* arg){
  // code is based on previous work: https://github.com/JustinOng/sumo/blob/master/software/sumo/src/configure_rmt.c
  // with reference to https://www.esp32.com/viewtopic.php?t=7116#p32383
  // but modified so that this ISR only checks chX_rx_end
  uint32_t intr_st = RMT.int_st.val;
  uint32_t channel_mask;
  channel_mask = BIT(RMT_Channel_Number+12); //use updated ESP32-S3 rmt_struct.h bit map, channel 4, 5, 6, 7's RX_END found in bits 16, 17, 18, 19. so the bit = channel +12

  if (!(intr_st & channel_mask)) return;
  RMT.chmconf[RMT_Channel_Number].conf1.rx_en_m = 0; //turn off receiver
  RMT.chmconf[RMT_Channel_Number].conf1.mem_owner_m = RMT_MEM_OWNER_TX; //change memory owner to protect data

  volatile rmt_item32_t* item = RMTMEM.chan[RMT_Channel_Number].data32;
  if (item) {
    PWM_period = item->duration0; //update the output data
  }

  RMT.chmconf[RMT_Channel_Number].conf1.mem_wr_rst_m = 1; //reset memory
  RMT.chmconf[RMT_Channel_Number].conf1.mem_owner_m = RMT_MEM_OWNER_RX; //return memory ownership to RX
  RMT.chmconf[RMT_Channel_Number].conf1.rx_en_m = 1;  //turn on receiver
  RMT.int_clr.val = channel_mask; //clear RMT interrupt status
}

void rmt_init(void) {
  rmt_config_t rmt_channel;
  PWM_period = RECEIVER_CH_CENTER; //default value
  rmt_channel.channel = (rmt_channel_t) RMT_Channel_Number; //select which RMT channel to use.  For ESP32S3, should be 4-7 for RX
  rmt_channel.gpio_num = (gpio_num_t) RMT_Connected_GPIO;
  rmt_channel.clk_div = RMT_RX_CLK_DIV;
  rmt_channel.mem_block_num = 1;
  rmt_channel.rmt_mode = RMT_MODE_RX;
  rmt_channel.rx_config.filter_en = true;
  rmt_channel.rx_config.filter_ticks_thresh = 100;
  rmt_channel.rx_config.idle_threshold = RMT_RX_MAX_US * RMT_TICK_PER_US;
  ESP_ERROR_CHECK(rmt_config(&rmt_channel));
  rmt_set_rx_intr_en(rmt_channel.channel, true); 

  //---------------------------------------------------
  rmt_rx_start(rmt_channel.channel, 1); //<====THIS LINE IS FAILING======>
  //---------------------------------------------------

  rmt_isr_register(rmt_isr_handler, NULL, 0, NULL); //register the ISR handler

}

Debug Logs.

Serial log: 

22:01:40.716 -> Guru Meditation Error: Core  1 panic'ed (StoreProhibited). Exception was unhandled.
22:01:40.716 -> 
22:01:40.716 -> Core  1 register dump:
22:01:40.716 -> PC      : 0x42002b41  PS      : 0x00060633  A0      : 0x82001728  A1      : 0x3fcebd20  
22:01:40.716 -> A2      : 0x00000004  A3      : 0x00000000  A4      : 0x00000030  A5      : 0x3fc91948  
22:01:40.750 -> A6      : 0x3fc9193c  A7      : 0x00000001  A8      : 0x00000000  A9      : 0x3fcebd00  
22:01:40.750 -> A10     : 0x60016000  A11     : 0x00010000  A12     : 0x00000001  A13     : 0x00060623  
22:01:40.750 -> A14     : 0x00060620  A15     : 0x00000001  SAR     : 0x00000010  EXCCAUSE: 0x0000001d  
22:01:40.750 -> EXCVADDR: 0x00000088  LBEG    : 0x400570e8  LEND    : 0x400570f3  LCOUNT  : 0xffffffff  
22:01:40.783 -> 
22:01:40.783 -> 
22:01:40.783 -> Backtrace: 0x42002b3e:0x3fcebd20 0x42001725:0x3fcebd50 0x42001757:0x3fcebda0 0x42001fde:0x3fcebdd0
22:01:40.783 -> 
22:01:40.783 -> 
22:01:40.783 -> 
22:01:40.783 -> 
22:01:40.783 -> ELF file SHA256: db3da357a1c44a9e
22:01:40.949 -> 
22:01:40.949 -> Rebooting...
22:01:40.949 -> ESP-ROM:esp32s3-20210327
22:01:40.949 -> Build:Mar 27 2021
22:01:40.949 -> rst:0xc (RTC_SW_CPU_RST),boot:0x28 (SPI_FAST_FLASH_BOOT)
22:01:40.949 -> Saved PC:0x42021a82
22:01:40.949 -> SPIWP:0xee
22:01:40.949 -> mode:DIO, clock div:1
22:01:40.949 -> load:0x3fce3808,len:0x44c
22:01:40.949 -> load:0x403c9700,len:0xbe4
22:01:40.949 -> load:0x403cc700,len:0x2a38
22:01:40.949 -> entry 0x403c98d4

-----------

Backtrace decoder output:
0x42002b3e: rmt_rx_start at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/hal/esp32s3/include/hal/rmt_ll.h line 132
0x42001725: rmt_init() at /home/me/Dropbox/Apps/ArduinoDroid/_2023_05_22_RMT_S3_superbasic/_2023_05_22_RMT_S3_superbasic.ino line 71
0x42001757: setup() at /home/me/Dropbox/Apps/ArduinoDroid/_2023_05_22_RMT_S3_superbasic/_2023_05_22_RMT_S3_superbasic.ino line 13
0x42001fde: loopTask(void*) at /home/me/.arduino15/packages/esp32/hardware/esp32/2.0.9/cores/esp32/main.cpp line 42

More Information.

You can see in my code that it's based off the work of others. I've modified the code and it's worked great with ESP32. I further modified it based on documentation to work with ESP32S3, but I think there might be a problem with the driver for RX-only applications.

suda-morris commented 1 year ago

Hi @zyxtia , the issue is that, in your code, you didn't call the rmt_driver_install to install the driver object, leading to the p_rmt_obj[channel] being a NULL pointer when you call rmt_rx_start.

suda-morris commented 1 year ago

BTW, if what you need is to get notified when RMT RX is done, then you can consider trying the new RMT driver, where you can call rmt_rx_register_event_callbacks to hook your callback function. Note, the new driver only exists on esp-idf 5.x

zyxtia commented 1 year ago

rmt_driver_install

Hi @suda-morris , thank you for the reply. The documentation states that if using a custom ISR handler (which I am), then I can't use rmt_driver_install. I tried it anyway (rmt_driver_install(rmt_channel.channel,0,0);), and while it allows rmt_rx_start to pass, the program won't actually work because the default handler is installed, instead of my custom routine. Now that you've pointed me in the right direction, it seems that I am not the first person or the second to have trouble with this, but I haven't been able to find a resolution.

Also worth noting that the code works fine on ESP32 (with some basic edits within the ISR handler), but not on ESP32S3, so that's why I'm really stumped. I think the documentation isn't quite clear on

Any help would be greatly appreciated.

zyxtia commented 1 year ago

BTW, if what you need is to get notified when RMT RX is done, then you can consider trying the new RMT driver, where you can call rmt_rx_register_event_callbacks to hook your callback function. Note, the new driver only exists on esp-idf 5.x

I'm using Arduino-ESP32 Core (currently 2.0.9), so I'll have to wait until Arduino-ESP32 Core 3.0 comes out :)

suda-morris commented 1 year ago

@zyxtia I'm afraid there's no easy fix for it. I can explain the difference you've observed between esp32 and esp32s3. Why rmt_rx_start can work on ESP32 but can't on ESP32S3? Because on ESP32, this SOC_RMT_SUPPORT_RX_PINGPONG is false, in the rmt_rx_start function, there's no code that will read the p_rmt_obj[channel]. So it's safe for esp32. But on esp32s3, the driver needs to reset some of the internal states, especially for a ping-pong receive.

zyxtia commented 1 year ago

@zyxtia I'm afraid there's no easy fix for it. I can explain the difference you've observed between esp32 and esp32s3. Why rmt_rx_start can work on ESP32 but can't on ESP32S3? Because on ESP32, this SOC_RMT_SUPPORT_RX_PINGPONG is false, in the rmt_rx_start function, there's no code that will read the p_rmt_obj[channel]. So it's safe for esp32. But on esp32s3, the driver needs to reset some of the internal states, especially for a ping-pong receive.

Hi @suda-morris , thanks for the reply. A few follow up questions:

1) you say that there is no "easy" way. Would it be possible to modify my local configuration, even if it means my build isn't distributable? I imagine that I could modify the default ISR to suit my needs. This would be a stopgap until ESP-IDF v5 is released for Arduino ESP32 Core (and assuming that it includes functionality that I can have a custom interrupt). I'm currently pursuing that option (modifying a local copy of rmt.c), with limited success, but I'll figure it out hopefully.

2) question/suggestion: the v4.4 documentation states that "It’s not recommended for users to register an interrupt handler in their applications". Based on what youve said, I would suggest that the documentation is wrong and should be updated to read "custom ISR is not supported in v4.4.4", or some clarification therein. The documentation is misleading in that it explains methods to attach a custom ISR, even though they don't work. Is there any chance v4.4.4 documentation would be updated, or is that frozen and the point is therefore moot because v5 works totally differently?

3) (off topic to this bug request, but relevant to my use case) my use case is to read 50hz servo PWM, but I need to minimize impact to the main processor. If the RMT can no longer support this on ESP32S3, is there a way to use the MCPWM peripheral as an input-only device? That might be another option for my use case, but I can't find documentation or examples that can help. Or maybe there is another suggestion you have. I really don't want to have manual timing of PWM signals in my main code (for various valid reasons).

Thank you! -zyxtia-

zyxtia commented 10 months ago

Following up, in case anyone stumbles across this and wants an answer:

a) I ended up downloading the v4.4 "rmt.c" file (https://github.com/espressif/esp-idf/blob/release/v4.4/components/driver/rmt.c), and modified the "rmt_driver_isr_default" section to make it do what I wanted my custom ISR to do. It's less than ideal (i have to keep my custom "rmt.c" file in the directory of my Sketch, and can't give it a different name), but it's working as intended.

b) I maintain that the documentation for ESP-IDF 4.4 is incorrect. As stated above, it says that "it’s not recommended for users to register an interrupt handler in their applications", but it should state that this functionality simply does not work. As such, i've updated the subject of this submission accordingly, and am leaving this issue open for now, hoping someone can make the documentation correction (i don't know how to update documentation, so I can't do it myself)