RMT input on ESP32-S3 not compatible with ESP32 code

zyxtia
Posts: 2
Joined: Mon May 15, 2023 4:55 am

RMT input on ESP32-S3 not compatible with ESP32 code

Postby zyxtia » Mon May 15, 2023 5:14 am

<edit>
There doesn't seem to be an example sketch for simple RMT input for measuring PWM period, specifically for ESP32-S3 boards. Not only would that solve my issue, but would allow more developers to use RMT peripheral. Looking for help getting ESP32-S3's RMT to measure and report on the period of a PWM signal.
</edit>


I've been working on a logger/controller for a remote control airplane. In order to work as I intend, it needs to be able to read the PWM commands going to servos. These are standard 50hz servos, where the duty cycle is generally 1ms to 2ms. I had the code working with an original ESP32 (using the M5Stack Atom Lite board for now), which I found here https://newscrewdriver.com/2021/04/05/n ... ng-rc-pwm/ (actual code link is here: https://github.com/JustinOng/sumo/blob/ ... gure_rmt.c). I was using Arduino IDE (1.8.19) with the ESP32 board package, build 1.0.5.

Recently, i decided to upgrade to ESP32-S3 boards. I updated my ESP32 package to version 2.0.9, because the 1.0.X didn't have support for the S3 boards. Sure enough, my code wouldn't even compile. After some trial and error, i was able to modify the code with "#ifdef"s so that it would compile the "original" way for ESP32, and with tweaked code for ESP32-S3. Problem is, it now compiles for ESP32-S3, but doesn't actually detect any pulses.

Below is the code for simply readying the PWM signal and printing it to Serial. Can anyone help? I don't fully understand what the RMT commands are doing, and that's where my problem is (I think). Thank you!!!

-Ian-

Code: Select all

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "driver/rmt.h"
#include "esp_log.h"

static const char* TAG = "RMT";


//define the pins to use

#if ARDUINO_M5Stack_ATOM
  #define channel_0_input_pin 21  //unused pin on M5Atom-GPS module  
  #define channel_1_input_pin 25  //unused pin on M5Atom-GPS module   
  #define spareInputPin 2         //this pin is not actually correct.  just a placeholder
#elif ARDUINO_M5Stack_ATOMS3   //THIS IS AN ESP32-S3 BASED BOARD
  #define channel_0_input_pin 39   //unused pin on M5Atom-GPS module (with AtomS3-lite installed)
  #define channel_1_input_pin 38   //unused pin on M5Atom-GPS module (with AtomS3-lite installed)
  #define spareInputPin 14          //this pin is NOT actually correct.  Just a placeholder 
#endif


#define RECEIVER_CHANNELS_NUM 3
volatile uint16_t ReceiverChannels[RECEIVER_CHANNELS_NUM] = {0};
const uint8_t RECEIVER_CHANNELS[RECEIVER_CHANNELS_NUM] = { 1, 2, 3 };
const uint8_t RECEIVER_GPIOS[RECEIVER_CHANNELS_NUM] = { channel_0_input_pin, channel_1_input_pin, spareInputPin }; 


void setup() {
  Serial.begin(115200);
  ESP_LOGI(TAG, "Started");
  rmt_init(); //initialize the PWM monitor

}

void loop() {
  Serial.print("CH0=");
  Serial.print(ReceiverChannels[0]/8);
  Serial.print("us \t CH1=");
  Serial.print(ReceiverChannels[1]/8);
  Serial.print("us \t CH2=");
  Serial.print(ReceiverChannels[2]/8);
  Serial.println("us");
}



// receiver pulse length ranges from 8000 to 16000 centered around 12000 (units: ticks)
#define RECEIVER_CH_MIN 8000
#define RECEIVER_CH_CENTER 12000
#define RECEIVER_CH_MAX 16000

//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
// determines how many clock cycles one "tick" is
// [1..255], source is generally 80MHz APB clk

#define RMT_RX_CLK_DIV (80000000/RMT_TICK_PER_US/1000000)
// time before receiver goes idle
#define RMT_RX_MAX_US 3500





static void rmt_isr_handler(void* arg){
    // 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;

    // see declaration of RMT.int_st:
    // takes the form of 
    // bit 0: ch0_tx_end
    // bit 1: ch0_rx_end
    // bit 2: ch0_err
    // bit 3: ch1_tx_end
    // bit 4: ch1_rx_end
    // ...
    // thus, check whether bit (channel*3 + 1) is set to identify
    // whether that channel has changed

    uint8_t i;
    for(i = 0; i < RECEIVER_CHANNELS_NUM; i++) {
        uint8_t channel = RECEIVER_CHANNELS[i];
        uint32_t channel_mask = BIT(channel*3+1);

        if (!(intr_st & channel_mask)) continue;

        #ifdef CONFIG_IDF_TARGET_ESP32S3   //NEW CODE FOR ESP32-S3 BOARD
          //if it's an S3 board, use different terminology
          RMT.chmconf[channel].conf1.rx_en_m = 0;  //NEW CODE FOR ESP32-S3 BOARD
          RMT.chmconf[channel].conf1.mem_owner_m = RMT_MEM_OWNER_TX;  //NEW CODE FOR ESP32-S3 BOARD
        #else
          RMT.conf_ch[channel].conf1.rx_en = 0;
          RMT.conf_ch[channel].conf1.mem_owner = RMT_MEM_OWNER_TX;
        #endif

        
        volatile rmt_item32_t* item = RMTMEM.chan[channel].data32;
        if (item) {
            ReceiverChannels[i] = item->duration0;
        }

        #ifdef CONFIG_IDF_TARGET_ESP32S3  //NEW CODE FOR ESP32-S3 BOARD
          //if it's an S3 board, use different terminology.  Updated code made using a mixture of guessing, and the following files/links:
          //  (ESP32 reference: https://github.com/espressif/esp-idf/blob/master/components/soc/esp32/include/soc/rmt_struct.h)
          //  (ESP32S3 compare: https://github.com/espressif/esp-idf/blob/master/components/soc/esp32s3/include/soc/rmt_struct.h)
          //  C:\Users\<USERNAME>\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.9\tools\sdk\esp32s3\include\soc\esp32s3\include\soc\rmt_struct.h
          //  C:\Users\<USERNAME>\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.9\tools\sdk\esp32s3\include\hal\esp32s3\include\hal\rmt_ll.h
          RMT.chmconf[channel].conf1.mem_wr_rst_m = 1;  //NEW CODE FOR ESP32-S3 BOARD
          RMT.chmconf[channel].conf1.mem_owner_m = RMT_MEM_OWNER_RX;  //NEW CODE FOR ESP32-S3 BOARD
          RMT.chmconf[channel].conf1.rx_en_m = 1;  //NEW CODE FOR ESP32-S3 BOARD
        #else
          RMT.conf_ch[channel].conf1.mem_wr_rst = 1;
          RMT.conf_ch[channel].conf1.mem_owner = RMT_MEM_OWNER_RX;
          RMT.conf_ch[channel].conf1.rx_en = 1;
        #endif

        //clear RMT interrupt status.
        RMT.int_clr.val = channel_mask;
    }
}

void rmt_init(void) {
    uint8_t i;

    rmt_config_t rmt_channels[RECEIVER_CHANNELS_NUM] = {};

    for (i = 0; i < RECEIVER_CHANNELS_NUM; i++) {
        ReceiverChannels[i] = RECEIVER_CH_CENTER;

        rmt_channels[i].channel = (rmt_channel_t) RECEIVER_CHANNELS[i];
        rmt_channels[i].gpio_num = (gpio_num_t) RECEIVER_GPIOS[i];
        rmt_channels[i].clk_div = RMT_RX_CLK_DIV;
        rmt_channels[i].mem_block_num = 1;
        rmt_channels[i].rmt_mode = RMT_MODE_RX;
        rmt_channels[i].rx_config.filter_en = true;
        rmt_channels[i].rx_config.filter_ticks_thresh = 100;
        rmt_channels[i].rx_config.idle_threshold = RMT_RX_MAX_US * RMT_TICK_PER_US;

        rmt_config(&rmt_channels[i]);
        rmt_set_rx_intr_en(rmt_channels[i].channel, true);
        rmt_rx_start(rmt_channels[i].channel, 1);
    }

    rmt_isr_register(rmt_isr_handler, NULL, 0, NULL);
    ESP_LOGI(TAG, "Init ISR on %d", xPortGetCoreID());
}

zyxtia
Posts: 2
Joined: Mon May 15, 2023 4:55 am

Re: RMT input on ESP32-S3 not compatible with ESP32 code

Postby zyxtia » Tue May 23, 2023 6:37 am

I realized that with ESP32-S3, the RMT is different, in that channels 0-3 are TX only, and 4-7 are RX only. So that's probably why I wasn't getting any data from pins 0-3 :lol: I reconfigured the sketch to use RX pins, and the rmt_init() function causes the chip to restart.

After much digging, I learned that the v4.4 implementation of rmt.c does not allow custom ISR handlers to be used. Huge deficiency, IMO. Frustratingly, the v4.4.4 documentation says that custom ISR handlers are "not recommended", but does NOT actually say that they don't work at all.

For what it's worth, my (in work) solution is modifying the rmt_driver_isr_default routine contained in rmt.c (with a local copy in my sketch folder) to use my custom ISR. Not a great solution, but will hopefully allow my project to work until ESP-IDF v5.0 comes to Arduino IDE (part of Arduino ESP32 Core v3.0), at which point the whole RMT driver is dramatically different and I'll have to figure this out all over again.

Who is online

Users browsing this forum: No registered users and 58 guests