RMT - simplest receive example working, but idle_threshold problem

Thildemar
Posts: 3
Joined: Mon Oct 07, 2019 4:22 pm

Re: RMT - simplest receive example working, but idle_threshold problem

Postby Thildemar » Wed Oct 09, 2019 1:29 pm

Thanks, I'll see if this works for my project. Cheers!

jw--rt
Posts: 7
Joined: Mon Jul 01, 2019 1:44 pm

Re: RMT - simplest receive example working, but idle_threshold problem

Postby jw--rt » Wed Nov 13, 2019 7:27 pm

Hello everyone,

That last solution works like a charm for me! It's a bit hacky, but whatever!
What I changed is: I just readout the first value in memory and immediately after that use:

Code: Select all

rmt_item32_t* item = nullptr; // NULL
rmt_rx_start(RMT_CHANNEL_N, true);

for(;;)
{
    item = (rmt_item32_t*) (RMT_CHANNEL_MEM(RMT_CHANNEL_N));
    // Do math with item->duration0 and item->duration1
    rmt_memory_rw_rst(RMT_CHANNEL_N);
    vTaskDelay(DELAY_TIME_MS/portTICK_PERIOD_MS);
}
Thank you @jcsbanks for putting your time into investigating that!

Additionally, it solves my problem in my post viewtopic.php?t=12873.
I just haven't thought about using the RMT module.


Best regard,

jw--rt
Last edited by jw--rt on Wed Nov 13, 2019 9:50 pm, edited 3 times in total.

jcsbanks
Posts: 305
Joined: Tue Mar 28, 2017 8:03 pm

Re: RMT - simplest receive example working, but idle_threshold problem

Postby jcsbanks » Wed Nov 13, 2019 9:12 pm

Is that loop ever going to be blocked/yield or just burn up CPU cycles?

jw--rt
Posts: 7
Joined: Mon Jul 01, 2019 1:44 pm

Re: RMT - simplest receive example working, but idle_threshold problem

Postby jw--rt » Wed Nov 13, 2019 9:50 pm

Hello,

good point. For completeness, I edited my answer.

Best regards,

jw--rt

jonasfehr
Posts: 1
Joined: Thu Jun 10, 2021 9:44 am

Re: RMT - simplest receive example working, but idle_threshold problem

Postby jonasfehr » Thu Jun 10, 2021 7:03 pm

Hi, i am desperately trying to clear the memory in order to make a new measurement. But it seems that all I try has no effect, just when I remove the connection and reconnect, meaning a signal break > idle time, I can manage to get a reset. Can anyone point me in the right direction? Right now I am just starting the RMT module, no interrupt nor ringbuffer, and use a function to read it's content.

Maxzillian
Posts: 8
Joined: Fri Apr 01, 2022 3:06 pm

Re: RMT - simplest receive example working, but idle_threshold problem

Postby Maxzillian » Wed Apr 06, 2022 6:25 pm

Hi all, I just wanted to add onto this thread with my solution. I was running into problems with the RMT falling into wait if the buffer overflowed or going to idle and not recovering if the input frequency dropped too low as well as poor resolution in measuring duty cycle across the range. Ultimately this example is able to measure from 5-6000 Hz with a 50% duty cycle. Duty cycle plays a part in the minimum frequency as a long high or low pulse can exceed the limits of the 16 bit counters.

This code handles those two error conditions as well as dynamically adjusts the clock prescaler to improve duty cycle resolution. If you don't need to measure as high of a frequency you can reduce the size of mem_block_num and/or decrease the loop rate of the task. Alternatively increase the number of memory blocks or the loop rate of the task to read higher frequencies (not tested). Ultimately max frequency is limited by the number of cycles you can count (64 cycles per memory block) between resets of the rmt rx memory.

In the header:

Code: Select all

#include "driver/rmt.h"
#include "soc/rmt_reg.h"
#include "freertos/task.h"
#include <driver/gpio.h>

#define IO_FI_1 GPIO_NUM_39
#define IO_FI_2 GPIO_NUM_36
#define IO_FI_3 GPIO_NUM_35

enum frequencyInput_t {FI_1, FI_2, FI_3 };

void frequencyInputBegin();
uint32_t getFrequency(frequencyInput_t index);
uint32_t getDutyCycle(frequencyInput_t index);
And the source code:

Code: Select all

#include <frequencyInput.hpp>

#define MIN(A,B) ((A)<=(B)?(A):(B))
#define MAX(A,B) ((A)>=(B)?(A):(B))
#define MEDIAN(A,B,C) ((B)<=(A)?(A):((B)>=(C)?(C):(B)))

#define FI_COUNT 3

typedef struct rmt_channel
{
    rmt_channel_t channel;
    gpio_num_t pin;
    uint32_t frequency; //Hz x10
    uint32_t dutyCycle; //% x10
} rmt_channel;

rmt_channel rmt_channels[FI_COUNT] = {
    {
        RMT_CHANNEL_0,
        IO_FI_1,
        0,
        0
    },
    {
        RMT_CHANNEL_1,
        IO_FI_2,
        0,
        0
    },
    {
        RMT_CHANNEL_2,
        IO_FI_3,
        0,
        0
    }
};

static void frequencyReadTask(void * pData)
{
    int idleCount = 0;
    
    while(1)
    { //infinite loop
        for (int i = 0; i < FI_COUNT; i++)
        {
            uint32_t status = 0;
            uint8_t state = 0;
            rmt_get_status(rmt_channels[i].channel, &status);
            state = (status & RMT_STATE_CH0) >> RMT_STATE_CH0_S;

            //check for error states
            if(status & RMT_MEM_OWNER_ERR_CH0)
            {
                /*
                ownership of the memory block is violated
                typically happens when frequency drops too low
                */
                rmt_set_memory_owner(rmt_channels[i].channel, RMT_MEM_OWNER_RX);
                rmt_channels[i].dutyCycle = 0;
                rmt_channels[i].frequency = 0;
            }
            if(status & RMT_MEM_FULL_CH0)
            {
                /*
                received more data that memory allows
                typically happens if we capture too many pulses for our given sample time
                */
                rmt_rx_memory_reset(rmt_channels[i].channel);
            }

            switch(state)
            {
                case 0x0: //idle
                {
                    /*
                    Sometimes we get here if the frequency suddenly chops and the divider setting
                    hasn't kept up. Check if it's not default and reset it.
                    */
                    uint8_t divider;
                    rmt_get_clk_div(rmt_channels[i].channel, &divider);
                    if(divider < UINT8_MAX)
                    {
                        rmt_set_clk_div(rmt_channels[i].channel, UINT8_MAX);
                        rmt_rx_memory_reset(rmt_channels[i].channel);
                    }

                    if(idleCount++)
                    {
                        //came into the idle loop more than once in a row, assume 0 Hz
                        rmt_channels[i].dutyCycle = 0;
                        rmt_channels[i].frequency = 0;
                    }
                    break;
                }
                case 0x3: //receive
                {
                    uint8_t cycleCount = status & 0x3F;
                    //check that we have more than one pulse captured to reduce jitter at low frequency
                    if(cycleCount > 1)
                    {
                        uint32_t highCount = 0;
                        uint32_t totalCount = 0;
                        uint32_t maxTick = 0;
                        uint8_t divider;

                        rmt_item32_t * item = (rmt_item32_t*) (RMT_CHANNEL_MEM(rmt_channels[i].channel));

                        //the RMT device may have captured multiple pulses, perform an average
                        for(int j = 1; j <= cycleCount; j++)
                        {
                            highCount += item->level0 ? item->duration0 : item->duration1;
                            totalCount += item->duration0 + item->duration1;
                            maxTick = MAX(maxTick, MAX(item->duration0, item->duration1));
                            item++;
                        }
                        highCount  /= cycleCount;
                        totalCount /= cycleCount;

                        //capture the clock divider to calculate frequency and DC
                        rmt_get_clk_div(rmt_channels[i].channel, &divider);

                        //offer some protection from garbage data with some basic checks
                        if(highCount && highCount != totalCount && totalCount > 2)
                        {
                            rmt_channels[i].dutyCycle =
                                static_cast<uint32_t>((1000u*highCount) / totalCount);
                            rmt_channels[i].frequency =
                                static_cast<uint32_t>((10.0f * APB_CLK_FREQ / divider) / totalCount);

                            /*
                            Recalculate the clock divider to stay near the target, this broadens the
                            frequency range we can accurately measure. Otherwise a low divider allows
                            us to accurately measure high frequencies, but limits our minimum. A high
                            divider lowers our minimum frequency, but reduces our accuracy at measuring
                            high frequencies.
                            */
                            divider = MIN(maxTick * divider / 12000, UINT8_MAX);
                            divider = MAX(1, divider);
                            rmt_set_clk_div(rmt_channels[i].channel, divider);
                        }

                        //reset the memory to capture the next series of pulses
                        rmt_rx_memory_reset(rmt_channels[i].channel);
                    }

                    idleCount = 0;
                    break;
                }
                case 0x4: //wait
                {
                    //rmt is stuck in wait set, change the idle threshold to 0 to reset it
                    int16_t temp = RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres;
                    RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres = 0;
                    RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres = temp;

                    idleCount = 0;
                    break;
                }
                case 0x1: //send
                case 0x2: //read memory
                default:
                break;
            }
        }
        vTaskDelay(pdMS_TO_TICKS(20));
    }
}

void frequencyInputBegin()
{
    /*
    set up the RMT module to monitor the three frequency inputs
    make use of two memory blocks per input so we can capture up to 128 cycles instead of just 64,
    this halves how often we need to grab and empty the memory before risk of overflow
    */
    for (int i = 0; i < FI_COUNT; i++)
    {
        rmt_config_t rmt_rx_config = {
            .rmt_mode = RMT_MODE_RX,
            .channel = rmt_channels[i].channel,
            .gpio_num = rmt_channels[i].pin,
            .clk_div = UINT8_MAX,
            .mem_block_num = 2,
            .flags = 0,
            .rx_config = {
                .idle_threshold = INT16_MAX,
                .filter_ticks_thresh = UINT8_MAX,
                .filter_en = false
            }
        };

        assert(ESP_OK == rmt_config(&rmt_rx_config));
        assert(ESP_OK == rmt_rx_start(rmt_channels[i].channel, true));
    }

    xTaskCreate (frequencyReadTask, "FIHandler", 2048, NULL, 4, NULL) ;
}

uint32_t getFrequency(frequencyInput_t index)
{
    return rmt_channels[index].frequency;
}

uint32_t getDutyCycle(frequencyInput_t index)
{
    return rmt_channels[index].dutyCycle;
}
Last edited by Maxzillian on Tue Jul 23, 2024 6:46 pm, edited 1 time in total.

reiner_0815
Posts: 2
Joined: Sat Apr 01, 2023 12:53 am

Re: RMT - simplest receive example working, but idle_threshold problem

Postby reiner_0815 » Sun Apr 02, 2023 10:06 pm

Hello all, would like to read in several PWM channels. The PWM frequency of the channels is about 50Hz.
The H-pulse width is 1-2ms
I need the H-pulse width as well as the exact frequency.
I use the code from @Maxzillian. However, only the first channel is always read correctly and I can't find the error.
It is always the first channel that is correct.
Changes in frequency or duty cycle have no effect on channel 2.
I really don't know what to do.

The example code is compiled in vscode with the Arduino framework.

Code: Select all

// main.cpp

#include <Arduino.h>

#include <Ticker.h>
#include "frequencyInput.hpp"

void setup()
{
  Serial.begin(115200);
  Serial.println("V0.1 310323");

  // set pins to input
  pinMode(GPIO_NUM_13, INPUT);
  pinMode(GPIO_NUM_14, INPUT);
  pinMode(GPIO_NUM_27, INPUT);
  
  frequencyInputBegin();
}

void loop()
{
  Serial.printf("Pin: 13 f: %lu d: %lu%% rawHighCount: %lu rawTotalCount: %lu divider; %hhu\n",
                getFrequency(FI_1) / 10,
                getDutyCycle(FI_1) / 10,
                getRawHighCount(FI_1),
                getRawTotalCount(FI_1),
                getClkDivider(FI_1));


  Serial.printf("Pin: 14 f: %lu d: %lu%% rawHighCount: %lu rawTotalCount: %lu divider; %hhu\n",
                getFrequency(FI_2) / 10,
                getDutyCycle(FI_2) / 10,
                getRawHighCount(FI_2),
                getRawTotalCount(FI_2),
                getClkDivider(FI_2));

  Serial.println("");

  vTaskDelay(1000);
}

Code: Select all

// frequencyInput.hpp

#include "driver/rmt.h"
#include "soc/rmt_reg.h"
#include "freertos/task.h"
#include <driver/gpio.h>

#define IO_FI_1 GPIO_NUM_13
#define IO_FI_2 GPIO_NUM_14
#define IO_FI_3 GPIO_NUM_27

enum frequencyInput_t {FI_1, FI_2, FI_3 };

void frequencyInputBegin();
uint32_t getFrequency(frequencyInput_t index);
uint32_t getDutyCycle(frequencyInput_t index);
uint32_t getRawTotalCount(frequencyInput_t index);
uint32_t getRawHighCount(frequencyInput_t index);
uint8_t getClkDivider(frequencyInput_t index);

Code: Select all

// frequencyInput.cpp

#include <frequencyInput.hpp>

#define MIN(A, B) ((A) <= (B) ? (A) : (B))
#define MAX(A, B) ((A) >= (B) ? (A) : (B))
#define MEDIAN(A, B, C) ((B) <= (A) ? (A) : ((B) >= (C) ? (C) : (B)))

#define FI_COUNT 3

#define RMT_CLK_DIV 80  // 80 MHz


typedef struct rmt_channel
{
    rmt_channel_t channel;
    gpio_num_t pin;
    uint32_t frequency;    // Hz x10
    uint32_t dutyCycle;    //% x10
    uint32_t rawHighCount; // raw High ticks
    uint32_t rawTotalCount;  // raw Low ticks
    uint8_t divider;         // actual channel devider
} rmt_channel;

rmt_channel rmt_channels[FI_COUNT] = {
    {RMT_CHANNEL_0,
     IO_FI_1,
     0,
     0,
     0,
     0,
     0},
    {RMT_CHANNEL_1,
     IO_FI_2,
     0,
     0,
     0,
     0,
     0},
    {RMT_CHANNEL_2,
     IO_FI_3,
     0,
     0,
     0,
     0,
     0}};

static void frequencyReadTask(void *pData)
{
    int idleCount = 0;

    while (1)
    { // infinite loop
        for (int i = 0; i < FI_COUNT; i++)
        {
            uint32_t status = 0;
            uint8_t state = 0;
            rmt_get_status(rmt_channels[i].channel, &status);

            state = (status & RMT_STATE_CH0) >> RMT_STATE_CH0_S;

            // check for error states
            if (status & RMT_MEM_OWNER_ERR_CH0)
            {
                /*
                ownership of the memory block is violated
                typically happens when frequency drops too low
                */
                rmt_set_memory_owner(rmt_channels[i].channel, RMT_MEM_OWNER_RX);
                rmt_channels[i].dutyCycle = 0;
                rmt_channels[i].frequency = 0;
            }
            if (status & RMT_MEM_FULL_CH0)
            {
                /*
                received more data that memory allows
                typically happens if we capture too many pulses for our given sample time
                */
                rmt_rx_memory_reset(rmt_channels[i].channel);
            }

            switch (state)
            {
            case 0x0: // idle
            {
                /*
                Sometimes we get here if the frequency suddenly chops and the divider setting
                hasn't kept up. Check if it's not default and reset it.
                */
                uint8_t divider;
                rmt_get_clk_div(rmt_channels[i].channel, &divider);
                if (divider < UINT8_MAX)
                {
                    rmt_set_clk_div(rmt_channels[i].channel, UINT8_MAX);
                    rmt_rx_memory_reset(rmt_channels[i].channel);
                }

                if (idleCount++)
                {
                    // came into the idle loop more than once in a row, assume 0 Hz
                    rmt_channels[i].dutyCycle = 0;
                    rmt_channels[i].frequency = 0;
                }
                break;
            }
            case 0x3: // receive
            {
                uint8_t cycleCount = status & 0xFF;
                // check that we have more than one pulse captured to reduce jitter at low frequency
                if (cycleCount > 1)
                {
                    uint32_t highCount = 0;
                    uint32_t totalCount = 0;
                    uint32_t maxTick = 0;
                    uint8_t divider;

                    rmt_item32_t *item = (rmt_item32_t *)(RMT_CHANNEL_MEM(rmt_channels[i].channel));

                    // the RMT device may have captured multiple pulses, perform an average
                    for (int j = 1; j <= cycleCount; j++)
                    {
                        highCount += item->level0 ? item->duration0 : item->duration1;
                        totalCount += item->duration0 + item->duration1;
                        maxTick = MAX(maxTick, MAX(item->duration0, item->duration1));
                        item++;
                    }
                    highCount /= cycleCount;
                    totalCount /= cycleCount;

                    // capture the clock divider to calculate frequency and DC
                    rmt_get_clk_div(rmt_channels[i].channel, &divider);

                    // offer some protection from garbage data with some basic checks
                    if (highCount && highCount != totalCount && totalCount > 2)
                    {
                        rmt_channels[i].dutyCycle =
                            static_cast<uint32_t>((1000u * highCount) / totalCount);
                        rmt_channels[i].frequency =
                            static_cast<uint32_t>((10.0f * APB_CLK_FREQ / divider) / totalCount);
                        rmt_channels[i].rawHighCount = 
                            static_cast<uint32_t>( highCount * divider / RMT_CLK_DIV); // highCount / (divider / 80)
                        rmt_channels[i].rawTotalCount =
                            static_cast<uint32_t>(totalCount * divider / RMT_CLK_DIV);
                        rmt_channels[i].divider = 
                            static_cast<uint8_t>(divider);
                        /*
                        Recalculate the clock divider to stay near the target, this broadens the
                        frequency range we can accurately measure. Otherwise a low divider allows
                        us to accurately measure high frequencies, but limits our minimum. A high
                        divider lowers our minimum frequency, but reduces our accuracy at measuring
                        high frequencies.
                        */
                        divider = MIN(maxTick * divider / 12000, UINT8_MAX);
                        divider = MAX(1, divider);
                        rmt_set_clk_div(rmt_channels[i].channel, divider);
                    }

                    // reset the memory to capture the next series of pulses
                    rmt_rx_memory_reset(rmt_channels[i].channel);
                }

                idleCount = 0;
                break;
            }
            case 0x4: // wait
            {
                // rmt is stuck in wait set, change the idle threshold to 0 to reset it
                int16_t temp = RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres;
                RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres = 0;
                RMT.conf_ch[rmt_channels[i].channel].conf0.idle_thres = temp;

                idleCount = 0;
                break;
            }
            case 0x1: // send
            case 0x2: // read memory
            default:
                break;
            }
        }
        vTaskDelay(pdMS_TO_TICKS(20));
    }
}

void frequencyInputBegin()
{
    /*
    set up the RMT module to monitor the three frequency inputs
    make use of two memory blocks per input so we can capture up to 128 cycles instead of just 64,
    this halves how often we need to grab and empty the memory before risk of overflow
    */
    for (int i = 0; i < FI_COUNT; i++)
    {
        rmt_config_t rmt_rx_config = {
            .rmt_mode = RMT_MODE_RX,
            .channel = rmt_channels[i].channel,
            .gpio_num = rmt_channels[i].pin,
            .clk_div = UINT8_MAX,
            .mem_block_num = 2,
            .flags = 0,
            .rx_config = {
                .idle_threshold = INT16_MAX,
                .filter_ticks_thresh = UINT8_MAX,
                .filter_en = false}};

        assert(ESP_OK == rmt_config(&rmt_rx_config));
        assert(ESP_OK == rmt_rx_start(rmt_channels[i].channel, true));
    }

    xTaskCreate(frequencyReadTask, "FIHandler", 2048, NULL, 4, NULL);
}

uint32_t getFrequency(frequencyInput_t index)
{
    return rmt_channels[index].frequency;
}

uint32_t getDutyCycle(frequencyInput_t index)
{
    return rmt_channels[index].dutyCycle;
}

uint32_t getRawHighCount(frequencyInput_t index)
{
    return rmt_channels[index].rawHighCount;
}

uint8_t getClkDivider(frequencyInput_t index)
{
    return rmt_channels[index].divider;
}

uint32_t getRawTotalCount(frequencyInput_t index)
{
    return rmt_channels[index].rawTotalCount;
}
Here are the exemplary outputs:

Code: Select all

       _  
pin13 | |________  p:1,4ms T:20ms
          __  
pin14 ___|  |_______ P:1,6ms  T:20ms
  
   
Pin: 13 f: 50 d: 6% rawHighCount: 1396 rawTotalCount: 19999 divider; 124
Pin: 14 f: 178 d: 86% rawHighCount: 4832 rawTotalCount: 5603 divider; 255

Pin: 13 f: 50 d: 6% rawHighCount: 1395 rawTotalCount: 19998 divider; 124
Pin: 14 f: 178 d: 86% rawHighCount: 4832 rawTotalCount: 5603 divider; 255

Pin: 13 f: 50 d: 6% rawHighCount: 1396 rawTotalCount: 19999 divider; 124
Pin: 14 f: 178 d: 86% rawHighCount: 4832 rawTotalCount: 5603 divider; 255

Maxzillian
Posts: 8
Joined: Fri Apr 01, 2022 3:06 pm

Re: RMT - simplest receive example working, but idle_threshold problem

Postby Maxzillian » Mon Jul 24, 2023 4:30 pm

Sorry for the delay; you may need to insert some print statements into the frequencyReadTask loop to see what the individual counters are doing. Namely pay attention to what the state is of each channel and see if the second and third are never hitting the receive state.

One thing I'm noticing is that despite your pin 14 input being at a higher frequency, the divider is stuck at 255. Now that shouldn't stop it from counting at all, but it is suspicious. That said, that only affects the accuracy and shouldn't stop it from counting at all. You should consider removing the divider and RMT_CLK_DIV factors from your rawHighCount and rawTotalCount statistics as you're just making it harder to diagnose as you're not really reporting the raw values in that case.

reiner_0815
Posts: 2
Joined: Sat Apr 01, 2023 12:53 am

Re: RMT - simplest receive example working, but idle_threshold problem

Postby reiner_0815 » Tue Jul 25, 2023 7:19 am

Thank you for your reply.
I have solved it this way in the meantime:
https://github.com/rewegit/esp32-rmt-pwm-reader
https://github.com/rewegit/esp32-rmt-pw ... ItWorks.md
I am doing very well with this in my application.

Thanks for pushing me in that direction.

Maxzillian
Posts: 8
Joined: Fri Apr 01, 2022 3:06 pm

Re: RMT - simplest receive example working, but idle_threshold problem

Postby Maxzillian » Tue Jul 23, 2024 6:46 pm

I did manage to figure out why my code snippet only works for the first RMT channel. I had an error in the cycle counter that was masked by 0xFF, but it should have been limited to 0x3F as RMT can only capture up to 64 pulses. This was leading to the other channels capturing part of the memory range indicated in the status register as the cycle count and leading to bad calculations. I've edited my earlier post.

Who is online

Users browsing this forum: Bing [Bot] and 402 guests